--[[
═══════════════════════════════════════════════════════════════════════════════
                              UNIVERSAL TADC
                  Dual-Coalition Tactical Air Defense Controller
                           Advanced Zone-Based System
═══════════════════════════════════════════════════════════════════════════════

DESCRIPTION:
This script provides a sophisticated automated air defense system for BOTH RED and 
BLUE coalitions operating independently. Features advanced zone-based area of 
responsibility (AOR) management, allowing squadrons to respond differently based 
on threat location and priority levels. Perfect for complex scenarios requiring 
realistic air defense behavior and tactical depth.

CORE FEATURES:
• Dual-coalition support with completely independent operation
• Advanced zone-based area of responsibility system (Primary/Secondary/Tertiary)
• Automatic threat detection with intelligent interceptor allocation
• Multi-squadron management with individual cooldowns and aircraft tracking
• Dynamic cargo aircraft replenishment system
• Configurable intercept ratios with zone-specific response modifiers
• Smart interceptor routing, engagement, and RTB (Return to Base) behavior
• Real-time airbase status monitoring (operational/captured/destroyed)
• Comprehensive configuration validation and error reporting
• Asymmetric warfare support with coalition-specific capabilities
• Emergency cleanup systems and safety nets for mission stability

ADVANCED ZONE SYSTEM:
Each squadron can be configured with up to three zone types:
• PRIMARY ZONE: Main area of responsibility (full response ratio)
• SECONDARY ZONE: Support area (reduced response, optional low-priority filtering)
• TERTIARY ZONE: Emergency/fallback area (enhanced response when base threatened)
• Squadrons will respond based on threat location relative to their zones
• Zone-specific response modifiers can be configured for each squadron
• Zones may overlap between squadrons for layered defense.  

ADVANCED ZONE SETUP:
• Create zones in the mission editor (MOOSE polygons, circles, etc.)
• Assign zone names to squadrons in the configuration (exact match required)
• Leave zones as nil for global threat response (no zone restrictions)
• Each zone is defined by placing a helicopter group with waypoints outlining the area
• The script will create polygon zones from the helicopter waypoints automatically

Zone response behaviors include:
• Distance-based engagement limits (max range from airbase)
• Priority thresholds for threat classification (major vs minor threats)
• Fallback conditions (auto-switch to tertiary when squadron weakened)
• Response ratio multipliers per zone type
• Low-priority threat filtering in secondary zones

REPLENISHMENT SYSTEM:
• Automated cargo aircraft detection system that monitors for transport aircraft
  flyovers to replenish squadron aircraft counts (fixed wing only):
• Detects cargo aircraft by name patterns (CARGO, TRANSPORT, C130, C-130, AN26, AN-26)
• Monitors flyover proximity to friendly airbases (no landing required)
• Replenishes squadron aircraft up to maximum capacity per airbase
• Prevents duplicate processing of the same cargo delivery
• Coalition-specific replenishment amounts configurable independently
• Supports sustained operations over extended mission duration

*** This system does not spawn or manage cargo aircraft - it only detects when
your existing cargo aircraft complete deliveries via flyover. Create and route your own
transport missions to maintain squadron strength. Aircraft can deliver supplies by
flying within 3000m of any configured airbase without needing to land. ***

INTERCEPT RATIO SYSTEM:
Sophisticated threat response calculation with zone-based modifiers:
• Base intercept ratio (e.g., 0.8 = 8 interceptors per 10 threats)
• Zone-specific multipliers (primary: 1.0, secondary: 0.6, tertiary: 1.4)
• Threat size considerations (larger formations get proportional response)
• Squadron selection based on zone priority and proximity
• Aircraft availability and cooldown status factored into decisions

SETUP INSTRUCTIONS:
1. Load MOOSE framework in mission before this script
2. Configure Squadrons: Create fighter aircraft GROUP templates for both coalitions in mission editor
3. Configure RED squadrons in RED_SQUADRON_CONFIG section
4. Configure BLUE squadrons in BLUE_SQUADRON_CONFIG section
5. Optionally create zones in mission editor for area-of-responsibility using helicopter groups with waypoints.
6. Set coalition behavior parameters in TADC_SETTINGS
7. Configure cargo patterns in ADVANCED_SETTINGS if using replenishment
8. Add this script as "DO SCRIPT" trigger at mission start (after MOOSE loaded)
9. Create and manage cargo aircraft missions for replenishment (optional)

CONFIGURATION VALIDATION:
Built-in validation system checks for:
• Template existence and proper naming
• Airbase name accuracy and coalition control
• Zone existence in mission editor
• Parameter ranges and logical consistency
• Coalition enablement and squadron availability
• Prevents common configuration errors before mission starts

TACTICAL SCENARIOS SUPPORTED:
• Balanced air warfare with equal capabilities and symmetric response
• Asymmetric scenarios with different coalition strengths and capabilities
• Layered air defense with overlapping squadron zones
• Border/perimeter defense with primary and fallback positions
• Training missions for AI vs AI air combat observation
• Dynamic frontline battles with shifting territorial control
• Long-duration missions with cargo resupply operations
• Emergency response scenarios with threat priority management

LOGGING AND MONITORING:
• Real-time threat detection and interceptor launch notifications
• Squadron status reports including aircraft counts and cooldown timers
• Airbase operational status with capture/destruction detection
• Cargo delivery tracking and replenishment confirmations
• Zone-based engagement decisions with detailed reasoning
• Configuration validation results and error reporting
• Performance monitoring with emergency cleanup notifications

REQUIREMENTS:
• MOOSE framework (https://github.com/FlightControl-Master/MOOSE)
• Fighter aircraft GROUP templates (not UNIT templates) for each coalition
• Airbases must exist in mission and be under correct coalition control
• Zone objects in mission editor (if using zone-based features)
• Proper template naming matching squadron configuration

AUTHOR:
• Based off MOOSE framework by FlightControl-Master
• Developed and customized by Mission Designer "F99th-TracerFacer"

VERSION: 1.0
═══════════════════════════════════════════════════════════════════════════════
]]

--[[
═══════════════════════════════════════════════════════════════════════════════
                                MAIN SETTINGS
═══════════════════════════════════════════════════════════════════════════════
]]

-- Core TADC behavior settings - applies to BOTH coalitions unless overridden
local TADC_SETTINGS = {
    -- Enable/Disable coalitions
    enableRed = true,            -- Set to false to disable RED TADC
    enableBlue = true,           -- Set to false to disable BLUE TADC
    
    -- Timing settings (applies to both coalitions)
    checkInterval = 30,          -- How often to scan for threats (seconds)
    monitorInterval = 30,        -- How often to check interceptor status (seconds)
    statusReportInterval = 1805,  -- How often to report airbase status (seconds)
    squadronSummaryInterval = 1800, -- How often to broadcast squadron summary (seconds)
    cargoCheckInterval = 15,     -- How often to check for cargo deliveries (seconds)
    
    -- RED Coalition Settings
    red = {
        maxActiveCAP = 24,           -- Maximum RED fighters airborne at once
        squadronCooldown = 600,      -- RED cooldown after squadron launch (seconds)
        interceptRatio = 1.2,        -- RED interceptors per threat aircraft
        cargoReplenishmentAmount = 4, -- RED aircraft added per cargo delivery
        emergencyCleanupTime = 7200, -- RED force cleanup time (seconds)
        rtbFlightBuffer = 300,       -- RED extra landing time before cleanup (seconds)
    },
    
    -- BLUE Coalition Settings  
    blue = {
        maxActiveCAP = 24,           -- Maximum BLUE fighters airborne at once
        squadronCooldown = 600,      -- BLUE cooldown after squadron launch (seconds)
        interceptRatio = 1.2,        -- BLUE interceptors per threat aircraft
        cargoReplenishmentAmount = 4, -- BLUE aircraft added per cargo delivery
        emergencyCleanupTime = 7200, -- BLUE force cleanup time (seconds)
        rtbFlightBuffer = 300,       -- BLUE extra landing time before cleanup (seconds)
    },
}


--[[
INTERCEPT RATIO CHART - How many interceptors launch per threat aircraft:

Threat Size:        1    2    4    8    12   16   (aircraft)
====================================================================
interceptRatio 0.2: 1    1    1    2     3    4   (conservative)
interceptRatio 0.5: 1    1    2    4     6    8   (light response)
interceptRatio 0.8: 1    2    4    7    10   13   (balanced) <- DEFAULT
interceptRatio 1.0: 1    2    4    8    12   16   (1:1 parity)
interceptRatio 1.2: 2    3    5   10    15   20   (slight advantage)
interceptRatio 1.4: 2    3    6   12    17   23   (good advantage)
interceptRatio 1.6: 2    4    7   13    20   26   (strong response)
interceptRatio 1.8: 2    4    8   15    22   29   (overwhelming)
interceptRatio 2.0: 2    4    8   16    24   32   (overkill)

TACTICAL EFFECTS:
• 0.2-0.5: Minimal response, may be overwhelmed by large formations
• 0.8-1.0: Realistic parity, creates balanced dogfights
• 1.2-1.4: Coalition advantage, challenging for enemy
• 1.6-1.8: Strong defense, difficult penetration missions
• 1.9-2.0: Nearly impenetrable, may exhaust squadrons quickly

SQUADRON IMPACT:
• Low ratios (0.2-0.8): Squadrons last longer, sustained defense
• High ratios (1.6-2.0): Rapid squadron depletion, coverage gaps
• Sweet spot (1.0-1.4): Balanced response with good coverage duration

ASYMMETRIC SCENARIOS:
• Set RED ratio 1.2, BLUE ratio 0.8 = RED advantage
• Set RED ratio 0.6, BLUE ratio 1.4 = BLUE advantage
• Different maxActiveCAP values create capacity imbalances
]]


--[[
═══════════════════════════════════════════════════════════════════════════════
                            ADVANCED SETTINGS
═══════════════════════════════════════════════════════════════════════════════

These settings control more detailed behavior. Most users won't need to change these.
]]

local ADVANCED_SETTINGS = {
    -- Cargo aircraft detection patterns (aircraft with these names will replenish squadrons (Currently only fixed wing aircraft supported)) 
    cargoPatterns = {"CARGO", "TRANSPORT", "C130", "C-130", "AN26", "AN-26"},
    
    -- Distance from airbase to consider cargo "delivered" via flyover (meters)
    -- Aircraft flying within this range will count as supply delivery (no landing required)
    cargoLandingDistance = 3000,
    -- Distance from airbase to consider a landing as delivered (wheel touchdown)
    -- Use a slightly larger radius than 1000m to account for runway offsets from airbase center
    cargoLandingEventRadius = 2000,
    
    -- Velocity below which aircraft is considered "landed" (km/h)
    cargoLandedVelocity = 5,
    
    -- RTB settings
    rtbAltitude = 6000,    -- Return to base altitude (feet)
    rtbSpeed = 430,        -- Return to base speed (knots)
    
    -- Logging settings
    enableDetailedLogging = false,  -- Set to false to reduce log spam
    logPrefix = "[Universal TADC]", -- Prefix for all log messages
    -- Proxy/raw-fallback verbose logging (set true to debug proxy behavior)
    verboseProxyLogging = false,
}

--[[
═══════════════════════════════════════════════════════════════════════════════
                              SYSTEM CODE
                    (DO NOT MODIFY BELOW THIS LINE)
═══════════════════════════════════════════════════════════════════════════════
]]



-- Internal tracking variables - separate for each coalition
local activeInterceptors = {
    red = {},
    blue = {}
}
local lastLaunchTime = {
    red = {},
    blue = {}
}
local assignedThreats = {
    red = {},
    blue = {}
}
local squadronCooldowns = {
    red = {},
    blue = {}
}
squadronAircraftCounts = {
    red = {},
    blue = {}
}

-- Aircraft spawn tracking for stuck detection
local aircraftSpawnTracking = {
    red = {}, -- groupName -> {spawnPos, spawnTime, squadron, airbase}
    blue = {}
}

-- Airbase health status
local airbaseHealthStatus = {
    red = {}, -- airbaseName -> "operational"|"stuck-aircraft"|"unusable"
    blue = {}
}

local function coalitionKeyFromSide(side)
    if side == coalition.side.RED then return "red" end
    if side == coalition.side.BLUE then return "blue" end
    return nil
end

local function cleanupInterceptorEntry(interceptorName, coalitionKey)
    if not interceptorName or not coalitionKey then return end
    if activeInterceptors[coalitionKey] then
        activeInterceptors[coalitionKey][interceptorName] = nil
    end
    if aircraftSpawnTracking[coalitionKey] then
        aircraftSpawnTracking[coalitionKey][interceptorName] = nil
    end
end

local function destroyInterceptorGroup(interceptor, coalitionKey, delaySeconds)
    if not interceptor then return end

    local name = nil
    if interceptor.GetName then
        local ok, value = pcall(function() return interceptor:GetName() end)
        if ok then name = value end
    end

    local resolvedKey = coalitionKey
    if not resolvedKey and interceptor.GetCoalition then
        local ok, side = pcall(function() return interceptor:GetCoalition() end)
        if ok then
            resolvedKey = coalitionKeyFromSide(side)
        end
    end

    local function doDestroy()
        if interceptor and interceptor.IsAlive and interceptor:IsAlive() then
            pcall(function() interceptor:Destroy() end)
        end
        if name and resolvedKey then
            cleanupInterceptorEntry(name, resolvedKey)
        end
    end

    if delaySeconds and delaySeconds > 0 then
        timer.scheduleFunction(function()
            doDestroy()
            return
        end, {}, timer.getTime() + delaySeconds)
    else
        doDestroy()
    end
end

local function finalizeCargoMission(cargoGroup, squadron, coalitionKey)
    if not cargoMissions or not coalitionKey or not squadron or not squadron.airbaseName then
        return
    end

    local coalitionBucket = cargoMissions[coalitionKey]
    if type(coalitionBucket) ~= "table" then
        return
    end

    local groupName = nil
    if cargoGroup and cargoGroup.GetName then
        local ok, value = pcall(function() return cargoGroup:GetName() end)
        if ok then groupName = value end
    end

    for idx = #coalitionBucket, 1, -1 do
        local mission = coalitionBucket[idx]
        if mission and mission.destination == squadron.airbaseName then
            local missionGroupName = nil
            if mission.group and mission.group.GetName then
                local ok, value = pcall(function() return mission.group:GetName() end)
                if ok then missionGroupName = value end
            end

            if not groupName or missionGroupName == groupName then
                mission.status = "completed"
                mission.completedAt = timer.getTime()

                if mission.group and mission.group.Destroy then
                    local targetGroup = mission.group
                    timer.scheduleFunction(function()
                        pcall(function()
                            if targetGroup and targetGroup.IsAlive and targetGroup:IsAlive() then
                                targetGroup:Destroy()
                            end
                        end)
                        return
                    end, {}, timer.getTime() + 90)
                end

                table.remove(coalitionBucket, idx)
            end
        end
    end
end

-- Logging function
local function log(message, detailed)
    if not detailed or ADVANCED_SETTINGS.enableDetailedLogging then
        env.info(ADVANCED_SETTINGS.logPrefix .. " " .. message)
    end
end

local function safeCoordinate(object)
    if not object or type(object) ~= "table" or not object.GetCoordinate then
        return nil
    end
    local ok, coord = pcall(function() return object:GetCoordinate() end)
    if ok and coord then
        return coord
    end
    return nil
end

-- Performance optimization: Cache SET_GROUP objects to avoid repeated creation
local cachedSets = {
    redCargo = nil,
    blueCargo = nil,
    redAircraft = nil,
    blueAircraft = nil
}

if type(RED_SQUADRON_CONFIG) ~= "table" then
    local msg = "CONFIG ERROR: RED_SQUADRON_CONFIG is missing or not loaded. Make sure Moose_TADC_SquadronConfigs_Load1st.lua is loaded before this script."
    log(msg, true)
    MESSAGE:New(msg, 30):ToAll()
end
if type(BLUE_SQUADRON_CONFIG) ~= "table" then
    local msg = "CONFIG ERROR: BLUE_SQUADRON_CONFIG is missing or not loaded. Make sure Moose_TADC_SquadronConfigs_Load1st.lua is loaded before this script."
    log(msg, true)
    MESSAGE:New(msg, 30):ToAll()
end

for _, squadron in pairs(RED_SQUADRON_CONFIG) do
    if squadron.aircraft and squadron.templateName then
        squadronAircraftCounts.red[squadron.templateName] = squadron.aircraft
    end
end

for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
    if squadron.aircraft and squadron.templateName then
        squadronAircraftCounts.blue[squadron.templateName] = squadron.aircraft
    end
end

-- Squadron resource summary generator

local function getSquadronResourceSummary(coalitionSide)
    local function getStatus(remaining, max, state)
        if state == "captured" then return "[CAPTURED]" end
        if state == "destroyed" then return "[DESTROYED]" end
        if state ~= "operational" then return "[OFFLINE]" end
        
        local percent = (remaining / max) * 100
        if percent <= 10 then return "[CRITICAL]" end
        if percent <= 25 then return "[LOW]" end
        return "OK"
    end

    local lines = {}
    table.insert(lines, "-=[ Tactical Air Defense Controller ]=-\n")
    table.insert(lines, "Squadron Resource Summary:\n")
    table.insert(lines, "| Squadron     | Aircraft Remaining | Status      |")
    table.insert(lines, "|--------------|--------------------|-------------|")

    if coalitionSide == coalition.side.RED then
        for _, squadron in pairs(RED_SQUADRON_CONFIG) do
            local remaining = squadronAircraftCounts.red[squadron.templateName] or 0
            local max = squadron.aircraft or 0
            local state = squadron.state or "operational"
            local status = getStatus(remaining, max, state)
            table.insert(lines, string.format("| %-13s | %2d / %-15d | %-11s |", squadron.displayName or squadron.templateName, remaining, max, status))
        end
    elseif coalitionSide == coalition.side.BLUE then
        for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
            local remaining = squadronAircraftCounts.blue[squadron.templateName] or 0
            local max = squadron.aircraft or 0
            local state = squadron.state or "operational"
            local status = getStatus(remaining, max, state)
            table.insert(lines, string.format("| %-13s | %2d / %-15d | %-11s |", squadron.displayName or squadron.templateName, remaining, max, status))
        end
    end

    table.insert(lines, "\n- [CAPTURED]: Airbase captured by enemy\n- [LOW]: Below 25%\n- [CRITICAL]: Below 10%\n- OK: Above 25%")
    return table.concat(lines, "\n")
end

-- Broadcast squadron summary to all players
local function broadcastSquadronSummary()
    if TADC_SETTINGS.enableRed then
        local summaryRed = getSquadronResourceSummary(coalition.side.RED)
        MESSAGE:New(summaryRed, 20):ToCoalition(coalition.side.RED)
    end
    if TADC_SETTINGS.enableBlue then
        local summaryBlue = getSquadronResourceSummary(coalition.side.BLUE)
        MESSAGE:New(summaryBlue, 20):ToCoalition(coalition.side.BLUE)
    end
end

-- Coalition-specific settings helper
local function getCoalitionSettings(coalitionSide)
    if coalitionSide == coalition.side.RED then
        return TADC_SETTINGS.red, "RED"
    elseif coalitionSide == coalition.side.BLUE then
        return TADC_SETTINGS.blue, "BLUE"
    else
        return nil, "UNKNOWN"
    end
end

-- Get squadron config for coalition
local function getSquadronConfig(coalitionSide)
    if coalitionSide == coalition.side.RED then
        return RED_SQUADRON_CONFIG
    elseif coalitionSide == coalition.side.BLUE then
        return BLUE_SQUADRON_CONFIG
    else
        return {}
    end
end

-- Check if coordinate is within a zone
local function isInZone(coordinate, zoneName)
    if not zoneName or zoneName == "" then
        return false
    end
    
    -- Try to find the zone
    local zone = ZONE:FindByName(zoneName)
    if zone then
        return zone:IsCoordinateInZone(coordinate)
    else
        -- Try to create polygon zone from helicopter group waypoints if not found
        local group = GROUP:FindByName(zoneName)
        if group then
            -- Create polygon zone using the group's waypoints as vertices
            zone = ZONE_POLYGON:NewFromGroupName(zoneName, zoneName)
            if zone then
                log("Created polygon zone '" .. zoneName .. "' from helicopter waypoints")
                return zone:IsCoordinateInZone(coordinate)
            else
                log("Warning: Could not create polygon zone from group '" .. zoneName .. "' - check waypoints")
            end
        else
            log("Warning: No group named '" .. zoneName .. "' found for zone creation")
        end
        
        log("Warning: Zone '" .. zoneName .. "' not found in mission and could not create from helicopter group", true)
        return false
    end
end

-- Get default zone configuration
local function getDefaultZoneConfig()
    return {
        primaryResponse = 1.0,
        secondaryResponse = 0.6,
        tertiaryResponse = 1.4,
        maxRange = 200,
        enableFallback = false,
        priorityThreshold = 4,
        ignoreLowPriority = false,
    }
end

-- Check if squadron should respond to fallback conditions
local function checkFallbackConditions(squadron, coalitionSide)
    local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
    
    -- Check if airbase is under attack (simplified - check if base has low aircraft)
    local currentAircraft = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0
    local maxAircraft = squadron.aircraft
    local aircraftRatio = currentAircraft / maxAircraft
    
    -- Trigger fallback if squadron is below 50% strength or base is threatened
    if aircraftRatio < 0.5 then
        return true
    end
    
    -- Could add more complex conditions here (base under attack, etc.)
    return false
end

-- Get threat zone priority and response ratio for squadron
local function getThreatZonePriority(threatCoord, squadron, coalitionSide)
    local zoneConfig = squadron.zoneConfig or getDefaultZoneConfig()
    
    -- Check distance from airbase first
    local airbase = AIRBASE:FindByName(squadron.airbaseName)
    if airbase then
        local airbaseCoord = airbase:GetCoordinate()
        local distance = airbaseCoord:Get2DDistance(threatCoord) / 1852 -- Convert meters to nautical miles
        
        if distance > zoneConfig.maxRange then
            return "none", 0, "out of range (" .. math.floor(distance) .. "nm > " .. zoneConfig.maxRange .. "nm)"
        end
    end
    
    -- Check tertiary zone first (highest priority if fallback enabled)
    if squadron.tertiaryZone and zoneConfig.enableFallback then
        if checkFallbackConditions(squadron, coalitionSide) then
            if isInZone(threatCoord, squadron.tertiaryZone) then
                return "tertiary", zoneConfig.tertiaryResponse, "fallback zone (enhanced response)"
            end
        end
    end
    
    -- Check primary zone
    if squadron.primaryZone and isInZone(threatCoord, squadron.primaryZone) then
        return "primary", zoneConfig.primaryResponse, "primary AOR"
    end
    
    -- Check secondary zone
    if squadron.secondaryZone and isInZone(threatCoord, squadron.secondaryZone) then
        return "secondary", zoneConfig.secondaryResponse, "secondary AOR"
    end
    
    -- Check tertiary zone (normal priority)
    if squadron.tertiaryZone and isInZone(threatCoord, squadron.tertiaryZone) then
        return "tertiary", zoneConfig.tertiaryResponse, "tertiary zone"
    end
    
    -- If no zones are defined, use global response
    if not squadron.primaryZone and not squadron.secondaryZone and not squadron.tertiaryZone then
        return "global", 1.0, "global response (no zones defined)"
    end
    
    -- Outside all defined zones
    return "none", 0, "outside defined zones"
end

-- Startup validation
local function validateConfiguration()
    local errors = {}
    
    -- Check coalition enablement
    if not TADC_SETTINGS.enableRed and not TADC_SETTINGS.enableBlue then
        table.insert(errors, "Both coalitions disabled - enable at least one in TADC_SETTINGS")
    end
    
    -- Validate RED squadrons if enabled
    if TADC_SETTINGS.enableRed then
        if #RED_SQUADRON_CONFIG == 0 then
            table.insert(errors, "No RED squadrons configured but RED TADC is enabled")
        else
            for i, squadron in pairs(RED_SQUADRON_CONFIG) do
                local prefix = "RED Squadron " .. i .. ": "
                
                if not squadron.templateName or squadron.templateName == "" or 
                   squadron.templateName == "RED_CAP_SQUADRON_1" or 
                   squadron.templateName == "RED_CAP_SQUADRON_2" then
                    table.insert(errors, prefix .. "templateName not configured or using default example")
                end
                
                if not squadron.displayName or squadron.displayName == "" then
                    table.insert(errors, prefix .. "displayName not configured")
                end
                
                if not squadron.airbaseName or squadron.airbaseName == "" or 
                   squadron.airbaseName:find("YOUR_RED_AIRBASE") then
                    table.insert(errors, prefix .. "airbaseName not configured or using default example")
                end
                
                if not squadron.aircraft or squadron.aircraft <= 0 then
                    table.insert(errors, prefix .. "aircraft count not configured or invalid")
                end
                
                -- Validate zone configuration if zones are specified
                if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then
                    if squadron.zoneConfig then
                        local zc = squadron.zoneConfig
                        if zc.primaryResponse and (zc.primaryResponse < 0 or zc.primaryResponse > 5) then
                            table.insert(errors, prefix .. "primaryResponse ratio out of range (0-5)")
                        end
                        if zc.secondaryResponse and (zc.secondaryResponse < 0 or zc.secondaryResponse > 5) then
                            table.insert(errors, prefix .. "secondaryResponse ratio out of range (0-5)")
                        end
                        if zc.tertiaryResponse and (zc.tertiaryResponse < 0 or zc.tertiaryResponse > 5) then
                            table.insert(errors, prefix .. "tertiaryResponse ratio out of range (0-5)")
                        end
                        if zc.maxRange and (zc.maxRange < 10 or zc.maxRange > 1000) then
                            table.insert(errors, prefix .. "maxRange out of range (10-1000 nm)")
                        end
                    end
                    
                    -- Check if specified zones exist in mission
                    local zones = {}
                    if squadron.primaryZone then table.insert(zones, squadron.primaryZone) end
                    if squadron.secondaryZone then table.insert(zones, squadron.secondaryZone) end
                    if squadron.tertiaryZone then table.insert(zones, squadron.tertiaryZone) end
                    
                    for _, zoneName in ipairs(zones) do
                        local zoneObj = ZONE:FindByName(zoneName)
                        if not zoneObj then
                            -- Check if there's a helicopter unit/group with this name for zone creation
                            local unit = UNIT:FindByName(zoneName)
                            local group = GROUP:FindByName(zoneName)
                            if not unit and not group then
                                table.insert(errors, prefix .. "zone '" .. zoneName .. "' not found in mission (no zone or helicopter unit named '" .. zoneName .. "')")
                            end
                        end
                    end
                end
            end
        end
    end
    
    -- Validate BLUE squadrons if enabled
    if TADC_SETTINGS.enableBlue then
        if #BLUE_SQUADRON_CONFIG == 0 then
            table.insert(errors, "No BLUE squadrons configured but BLUE TADC is enabled")
        else
            for i, squadron in pairs(BLUE_SQUADRON_CONFIG) do
                local prefix = "BLUE Squadron " .. i .. ": "
                
                if not squadron.templateName or squadron.templateName == "" or 
                   squadron.templateName == "BLUE_CAP_SQUADRON_1" or 
                   squadron.templateName == "BLUE_CAP_SQUADRON_2" then
                    table.insert(errors, prefix .. "templateName not configured or using default example")
                end
                
                if not squadron.displayName or squadron.displayName == "" then
                    table.insert(errors, prefix .. "displayName not configured")
                end
                
                if not squadron.airbaseName or squadron.airbaseName == "" or 
                   squadron.airbaseName:find("YOUR_BLUE_AIRBASE") then
                    table.insert(errors, prefix .. "airbaseName not configured or using default example")
                end
                
                if not squadron.aircraft or squadron.aircraft <= 0 then
                    table.insert(errors, prefix .. "aircraft count not configured or invalid")
                end
                
                -- Validate zone configuration if zones are specified
                if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then
                    if squadron.zoneConfig then
                        local zc = squadron.zoneConfig
                        if zc.primaryResponse and (zc.primaryResponse < 0 or zc.primaryResponse > 5) then
                            table.insert(errors, prefix .. "primaryResponse ratio out of range (0-5)")
                        end
                        if zc.secondaryResponse and (zc.secondaryResponse < 0 or zc.secondaryResponse > 5) then
                            table.insert(errors, prefix .. "secondaryResponse ratio out of range (0-5)")
                        end
                        if zc.tertiaryResponse and (zc.tertiaryResponse < 0 or zc.tertiaryResponse > 5) then
                            table.insert(errors, prefix .. "tertiaryResponse ratio out of range (0-5)")
                        end
                        if zc.maxRange and (zc.maxRange < 10 or zc.maxRange > 1000) then
                            table.insert(errors, prefix .. "maxRange out of range (10-1000 nm)")
                        end
                    end
                    
                    -- Check if specified zones exist in mission
                    local zones = {}
                    if squadron.primaryZone then table.insert(zones, squadron.primaryZone) end
                    if squadron.secondaryZone then table.insert(zones, squadron.secondaryZone) end
                    if squadron.tertiaryZone then table.insert(zones, squadron.tertiaryZone) end
                    
                    for _, zoneName in ipairs(zones) do
                        local zoneObj = ZONE:FindByName(zoneName)
                        if not zoneObj then
                            -- Check if there's a helicopter unit/group with this name for zone creation
                            local unit = UNIT:FindByName(zoneName)
                            local group = GROUP:FindByName(zoneName)
                            if not unit and not group then
                                table.insert(errors, prefix .. "zone '" .. zoneName .. "' not found in mission (no zone or helicopter unit named '" .. zoneName .. "')")
                            end
                        end
                    end
                end
            end
        end
    end
    
    -- Report errors
    if #errors > 0 then
    log("CONFIGURATION ERRORS DETECTED:")
    MESSAGE:New("CONFIGURATION ERRORS DETECTED:", 30):ToAll()
        for _, error in pairs(errors) do
            log("  ✗ " .. error)
            MESSAGE:New("CONFIG ERROR: " .. error, 30):ToAll()
        end
    log("Please fix configuration before using Universal TADC!")
    MESSAGE:New("Please fix configuration before using Universal TADC!", 30):ToAll()
        return false
    else
    log("Configuration validation passed ✓")
    MESSAGE:New("Universal TADC configuration passed ✓", 10):ToAll()
        return true
    end
end

-- Process cargo delivery for a squadron
local function processCargoDelivery(cargoGroup, squadron, coalitionSide, coalitionKey)
    -- Simple delivery processor: dedupe by group ID and credit supplies directly.
    if not _G.processedDeliveries then
        _G.processedDeliveries = {}
    end

    -- Use group ID + squadron airbase as dedupe key to avoid double crediting when the same group
    -- triggers multiple events or moves between airbases rapidly.
    local okId, grpId = pcall(function() return cargoGroup and cargoGroup.GetID and cargoGroup:GetID() end)
    local groupIdStr = (okId and grpId) and tostring(grpId) or "<no-id>"
    local deliveryKey = groupIdStr .. "_" .. tostring(squadron.airbaseName)

    -- Diagnostic log: show group name, id, and delivery key when processor invoked
    local okName, grpName = pcall(function() return cargoGroup and cargoGroup.GetName and cargoGroup:GetName() end)
    local groupNameStr = (okName and grpName) and tostring(grpName) or "<no-name>"
    log("PROCESS CARGO: invoked for group=" .. groupNameStr .. " id=" .. groupIdStr .. " targetAirbase=" .. tostring(squadron.airbaseName) .. " deliveryKey=" .. deliveryKey, true)

    if _G.processedDeliveries[deliveryKey] then
        -- Already processed recently, ignore
        log("PROCESS CARGO: deliveryKey " .. deliveryKey .. " already processed at " .. tostring(_G.processedDeliveries[deliveryKey]), true)
        return
    end

    -- Mark processed immediately
    _G.processedDeliveries[deliveryKey] = timer.getTime()

    -- Credit the squadron
    local currentCount = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0
    local maxCount = squadron.aircraft or 0
    local addAmount = TADC_SETTINGS[coalitionKey].cargoReplenishmentAmount or 0
    local newCount = math.min(currentCount + addAmount, maxCount)
    local actualAdded = newCount - currentCount

    if actualAdded > 0 then
        squadronAircraftCounts[coalitionKey][squadron.templateName] = newCount
        local msg = coalitionKey:upper() .. " CARGO DELIVERY: " .. cargoGroup:GetName() .. " delivered " .. actualAdded ..
            " aircraft to " .. (squadron.displayName or squadron.templateName) ..
            " (" .. newCount .. "/" .. maxCount .. ")"
        log(msg)
        MESSAGE:New(msg, 20):ToCoalition(coalitionSide)
        USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide)
    else
        local msg = coalitionKey:upper() .. " CARGO DELIVERY: " .. (squadron.displayName or squadron.templateName) .. " already at max capacity"
        log(msg, true)
        MESSAGE:New(msg, 10):ToCoalition(coalitionSide)
        USERSOUND:New("Cargo_Delivered.ogg"):ToCoalition(coalitionSide)
    end

    finalizeCargoMission(cargoGroup, squadron, coalitionKey)
end

-- Event handler for cargo aircraft landing (backup for actual landings)
local cargoEventHandler = {}
function cargoEventHandler:onEvent(event)
    if event.id == world.event.S_EVENT_LAND then
        local unit = event.initiator
        
        -- Safe unit name retrieval
        local unitName = "unknown"
        if unit and type(unit) == "table" then
            local ok, name = pcall(function() return unit:GetName() end)
            if ok and name then
                unitName = name
            end
        end
        
        log("LANDING EVENT: Received S_EVENT_LAND for unit: " .. unitName, true)
        
        if unit and type(unit) == "table" and unit.IsAlive and unit:IsAlive() then
            local group = unit:GetGroup()
            if group and type(group) == "table" and group.IsAlive and group:IsAlive() then
                -- Safe group name retrieval
                local cargoName = "unknown"
                local ok, name = pcall(function() return group:GetName():upper() end)
                if ok and name then
                    cargoName = name
                end
                
                log("LANDING EVENT: Processing group: " .. cargoName, true)
                
                local isCargoAircraft = false
                
                -- Check if aircraft name matches cargo patterns
                for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do
                    if string.find(cargoName, pattern) then
                        isCargoAircraft = true
                        log("LANDING EVENT: Matched cargo pattern '" .. pattern .. "' for " .. cargoName, true)
                        break
                    end
                end
                
                if isCargoAircraft then
                    -- Safe coordinate and coalition retrieval
                    local cargoCoord = nil
                    local ok, coord = pcall(function() return unit:GetCoordinate() end)
                    if ok and coord then
                        cargoCoord = coord
                    end
                    
                    log("LANDING EVENT: Cargo aircraft " .. cargoName .. " at coord: " .. tostring(cargoCoord), true)

                    if cargoCoord then
                        local closestAirbase = nil
                        local closestDistance = math.huge
                        local closestSquadron = nil

                        -- Search RED squadron configs
                        for _, squadron in pairs(RED_SQUADRON_CONFIG) do
                            local airbase = AIRBASE:FindByName(squadron.airbaseName)
                            if airbase then
                                local distance = cargoCoord:Get2DDistance(airbase:GetCoordinate())
                                log("LANDING EVENT: Checking distance to " .. squadron.airbaseName .. ": " .. math.floor(distance) .. "m", true)
                                if distance < closestDistance then
                                    closestDistance = distance
                                    closestAirbase = airbase
                                    closestSquadron = squadron
                                end
                            end
                        end

                        -- Search BLUE squadron configs
                        for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
                            local airbase = AIRBASE:FindByName(squadron.airbaseName)
                            if airbase then
                                local distance = cargoCoord:Get2DDistance(airbase:GetCoordinate())
                                log("LANDING EVENT: Checking distance to " .. squadron.airbaseName .. ": " .. math.floor(distance) .. "m", true)
                                if distance < closestDistance then
                                    closestDistance = distance
                                    closestAirbase = airbase
                                    closestSquadron = squadron
                                end
                            end
                        end

                        if closestAirbase then
                            local abCoalition = closestAirbase:GetCoalition()
                            local coalitionKey = (abCoalition == coalition.side.RED) and "red" or "blue"
                            if closestDistance < ADVANCED_SETTINGS.cargoLandingEventRadius then
                                log("LANDING DELIVERY: " .. cargoName .. " landed and delivered at " .. closestSquadron.airbaseName .. " (distance: " .. math.floor(closestDistance) .. "m)")
                                processCargoDelivery(group, closestSquadron, abCoalition, coalitionKey)
                            else
                                log("LANDING DETECTED: " .. cargoName .. " landed but no valid airbase found within range (closest: " .. (closestDistance and math.floor(closestDistance) .. "m" or "none") .. ")")
                            end
                        else
                            log("LANDING DETECTED: " .. cargoName .. " landed but no configured squadron airbases available to check", true)
                        end
                    else
                        log("LANDING EVENT: Could not get coordinates for cargo aircraft " .. cargoName, true)
                    end
                else
                    log("LANDING EVENT: " .. cargoName .. " is not a cargo aircraft", true)
                end
            else
                log("LANDING EVENT: Group is nil or not alive", true)
            end
        else
            -- Fallback: unit was nil or not alive (race/despawn). Try to retrieve group and name safely
            log("LANDING EVENT: Unit is nil or not alive - attempting fallback group retrieval", true)

            local fallbackGroup = nil
            local okGetGroup, grp = pcall(function()
                if unit and type(unit) == "table" and unit.GetGroup then
                    return unit:GetGroup()
                end
                -- Try event.initiator (may be raw DCS object)
                if event and event.initiator and type(event.initiator) == 'table' and event.initiator.GetGroup then
                    return event.initiator:GetGroup()
                end
                return nil
            end)

            if okGetGroup and grp then
                fallbackGroup = grp
            end

            if fallbackGroup then
                -- Try to get group name even if group:IsAlive() is false
                local okName, gname = pcall(function() return fallbackGroup:GetName():upper() end)
                local cargoName = "unknown"
                if okName and gname then
                    cargoName = gname
                end

                log("LANDING EVENT (fallback): Processing group: " .. cargoName, true)

                local isCargoAircraft = false
                for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do
                    if string.find(cargoName, pattern) then
                        isCargoAircraft = true
                        log("LANDING EVENT (fallback): Matched cargo pattern '" .. pattern .. "' for " .. cargoName, true)
                        break
                    end
                end

                if isCargoAircraft then
                    -- Try to get coordinate and coalition via multiple safe methods
                    local cargoCoord = nil
                    local okCoord, coord = pcall(function()
                        if unit and unit.GetCoordinate then return unit:GetCoordinate() end
                        if fallbackGroup and fallbackGroup.GetCoordinate then return fallbackGroup:GetCoordinate() end
                        return nil
                    end)
                    if okCoord and coord then cargoCoord = coord end

                    log("LANDING EVENT (fallback): Cargo aircraft " .. cargoName .. " at coord: " .. tostring(cargoCoord), true)

                    if cargoCoord then
                        local closestAirbase = nil
                        local closestDistance = math.huge
                        local closestSquadron = nil

                        for _, squadron in pairs(RED_SQUADRON_CONFIG) do
                            local airbase = AIRBASE:FindByName(squadron.airbaseName)
                            if airbase then
                                local distance = cargoCoord:Get2DDistance(airbase:GetCoordinate())
                                log("LANDING EVENT (fallback): Checking distance to " .. squadron.airbaseName .. ": " .. math.floor(distance) .. "m", true)
                                if distance < closestDistance then
                                    closestDistance = distance
                                    closestAirbase = airbase
                                    closestSquadron = squadron
                                end
                            end
                        end

                        for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
                            local airbase = AIRBASE:FindByName(squadron.airbaseName)
                            if airbase then
                                local distance = cargoCoord:Get2DDistance(airbase:GetCoordinate())
                                log("LANDING EVENT (fallback): Checking distance to " .. squadron.airbaseName .. ": " .. math.floor(distance) .. "m", true)
                                if distance < closestDistance then
                                    closestDistance = distance
                                    closestAirbase = airbase
                                    closestSquadron = squadron
                                end
                            end
                        end

                        if closestAirbase then
                            local abCoalition = closestAirbase:GetCoalition()
                            local coalitionKey = (abCoalition == coalition.side.RED) and "red" or "blue"
                            if closestDistance < ADVANCED_SETTINGS.cargoLandingEventRadius then
                                log("LANDING DELIVERY (fallback): " .. cargoName .. " landed and delivered at " .. closestSquadron.airbaseName .. " (distance: " .. math.floor(closestDistance) .. "m)")
                                processCargoDelivery(fallbackGroup, closestSquadron, abCoalition, coalitionKey)
                            else
                                log("LANDING DETECTED (fallback): " .. cargoName .. " landed but no valid airbase found within range (closest: " .. (closestDistance and math.floor(closestDistance) .. "m" or "none") .. ")")
                            end
                        else
                            log("LANDING EVENT (fallback): No configured squadron airbases available to check", true)
                        end
                    else
                        log("LANDING EVENT (fallback): Could not get coordinates for cargo aircraft " .. cargoName, true)
                    end
                else
                    log("LANDING EVENT (fallback): " .. cargoName .. " is not a cargo aircraft", true)
                end
            else
                log("LANDING EVENT: Fallback group retrieval failed", true)
                -- Additional fallback: try raw DCS object methods (lowercase) and resolve by name
                local okRaw, rawGroup = pcall(function()
                    if event and event.initiator and type(event.initiator) == 'table' and event.initiator.getGroup then
                        return event.initiator:getGroup()
                    end
                    return nil
                end)

                if okRaw and rawGroup then
                    -- Try to get raw group name
                    local okRawName, rawName = pcall(function()
                        if rawGroup.getName then return rawGroup:getName() end
                        return nil
                    end)

                    if okRawName and rawName then
                        local rawNameUp = tostring(rawName):upper()
                        log("LANDING EVENT: Resolved raw DCS group name: " .. rawNameUp, true)

                        -- Try to find MOOSE GROUP by that name
                        local okFind, mooseGroup = pcall(function() return GROUP:FindByName(rawNameUp) end)
                        if okFind and mooseGroup and type(mooseGroup) == 'table' then
                            log("LANDING EVENT: Found MOOSE GROUP for raw name: " .. rawNameUp, true)
                            -- Reuse the fallback logic using mooseGroup
                            local cargoName = rawNameUp
                            local isCargoAircraft = false
                            for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do
                                if string.find(cargoName, pattern) then
                                    isCargoAircraft = true
                                    break
                                end
                            end
                            if isCargoAircraft then
                                -- Try to get coordinate from raw group if possible
                                local cargoCoord = nil
                                local okPoint, point = pcall(function()
                                    if rawGroup.getController then
                                        -- Raw DCS unit list -> first unit point
                                        local dcs = rawGroup
                                        if dcs.getUnits then
                                            local units = dcs:getUnits()
                                            if units and #units > 0 and units[1].getPoint then
                                                return units[1]:getPoint()
                                            end
                                        end
                                    end
                                    return nil
                                end)
                                if okPoint and point then cargoCoord = point end

                                -- If we have a coordinate, find nearest squadron and process
                                if cargoCoord then
                                    local closestAirbase = nil
                                    local closestDistance = math.huge
                                    local closestSquadron = nil

                                    for _, squadron in pairs(RED_SQUADRON_CONFIG) do
                                        local airbase = AIRBASE:FindByName(squadron.airbaseName)
                                        if airbase then
                                            local distance = math.huge
                                            if type(cargoCoord) == 'table' and cargoCoord.Get2DDistance then
                                                local okDist, d = pcall(function() return cargoCoord:Get2DDistance(airbase:GetCoordinate()) end)
                                                if okDist and d then distance = d end
                                            else
                                                local okVec, aVec = pcall(function() return airbase:GetCoordinate():GetVec2() end)
                                                if okVec and aVec and type(aVec) == 'table' then
                                                    local cx, cy
                                                    if cargoCoord.x and cargoCoord.z then
                                                        cx, cy = cargoCoord.x, cargoCoord.z
                                                    elseif cargoCoord.x and cargoCoord.y then
                                                        cx, cy = cargoCoord.x, cargoCoord.y
                                                    elseif cargoCoord[1] and cargoCoord[3] then
                                                        cx, cy = cargoCoord[1], cargoCoord[3]
                                                    elseif cargoCoord[1] and cargoCoord[2] then
                                                        cx, cy = cargoCoord[1], cargoCoord[2]
                                                    end
                                                    if cx and cy then
                                                        local dx = cx - aVec.x
                                                        local dy = cy - aVec.y
                                                        distance = math.sqrt(dx*dx + dy*dy)
                                                    end
                                                end
                                            end

                                            if distance < closestDistance then
                                                closestDistance = distance
                                                closestAirbase = airbase
                                                closestSquadron = squadron
                                            end
                                        end
                                    end

                                    for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
                                        local airbase = AIRBASE:FindByName(squadron.airbaseName)
                                        if airbase then
                                            local distance = math.huge
                                            if type(cargoCoord) == 'table' and cargoCoord.Get2DDistance then
                                                local okDist, d = pcall(function() return cargoCoord:Get2DDistance(airbase:GetCoordinate()) end)
                                                if okDist and d then distance = d end
                                            else
                                                local okVec, aVec = pcall(function() return airbase:GetCoordinate():GetVec2() end)
                                                if okVec and aVec and type(aVec) == 'table' then
                                                    local cx, cy
                                                    if cargoCoord.x and cargoCoord.z then
                                                        cx, cy = cargoCoord.x, cargoCoord.z
                                                    elseif cargoCoord.x and cargoCoord.y then
                                                        cx, cy = cargoCoord.x, cargoCoord.y
                                                    elseif cargoCoord[1] and cargoCoord[3] then
                                                        cx, cy = cargoCoord[1], cargoCoord[3]
                                                    elseif cargoCoord[1] and cargoCoord[2] then
                                                        cx, cy = cargoCoord[1], cargoCoord[2]
                                                    end
                                                    if cx and cy then
                                                        local dx = cx - aVec.x
                                                        local dy = cy - aVec.y
                                                        distance = math.sqrt(dx*dx + dy*dy)
                                                    end
                                                end
                                            end

                                            if distance < closestDistance then
                                                closestDistance = distance
                                                closestAirbase = airbase
                                                closestSquadron = squadron
                                            end
                                        end
                                    end

                                    if closestAirbase and closestDistance < ADVANCED_SETTINGS.cargoLandingEventRadius then
                                        local abCoalition = closestAirbase:GetCoalition()
                                        local coalitionKey = (abCoalition == coalition.side.RED) and "red" or "blue"
                                        log("LANDING DELIVERY (raw-fallback): " .. rawNameUp .. " landed and delivered at " .. closestSquadron.airbaseName .. " (distance: " .. math.floor(closestDistance) .. "m)")
                                        processCargoDelivery(mooseGroup, closestSquadron, abCoalition, coalitionKey)
                                    else
                                        log("LANDING DETECTED (raw-fallback): " .. rawNameUp .. " landed but no valid airbase found within range (closest: " .. (closestDistance and math.floor(closestDistance) .. "m" or "none") .. ")")
                                    end
                                else
                                    log("LANDING EVENT: Could not extract coordinate from raw DCS group: " .. tostring(rawName), true)
                                end
                            else
                                log("LANDING EVENT: Raw group " .. tostring(rawName) .. " is not a cargo aircraft", true)
                            end
                        else
                            log("LANDING EVENT: Could not find MOOSE GROUP for raw name: " .. tostring(rawName) .. " - attempting raw-group proxy processing", true)

                            -- Even if we can't find a MOOSE GROUP, try to extract coordinates from the raw DCS group
                            local okPoint2, point2 = pcall(function()
                                if rawGroup and rawGroup.getUnits then
                                    local units = rawGroup:getUnits()
                                    if units and #units > 0 and units[1].getPoint then
                                        return units[1]:getPoint()
                                    end
                                end
                                return nil
                            end)

                            if okPoint2 and point2 then
                                local cargoCoord = point2
                                -- Find nearest configured squadron airbase (RED + BLUE)
                                local closestAirbase = nil
                                local closestDistance = math.huge
                                local closestSquadron = nil

                                for _, squadron in pairs(RED_SQUADRON_CONFIG) do
                                    local airbase = AIRBASE:FindByName(squadron.airbaseName)
                                    if airbase then
                                        local distance = math.huge
                                        local okVec, aVec = pcall(function() return airbase:GetCoordinate():GetVec2() end)
                                        if okVec and aVec and type(aVec) == 'table' then
                                            local cx, cy
                                            if cargoCoord.x and cargoCoord.z then
                                                cx, cy = cargoCoord.x, cargoCoord.z
                                            elseif cargoCoord.x and cargoCoord.y then
                                                cx, cy = cargoCoord.x, cargoCoord.y
                                            elseif cargoCoord[1] and cargoCoord[3] then
                                                cx, cy = cargoCoord[1], cargoCoord[3]
                                            elseif cargoCoord[1] and cargoCoord[2] then
                                                cx, cy = cargoCoord[1], cargoCoord[2]
                                            end
                                            if cx and cy then
                                                local dx = cx - aVec.x
                                                local dy = cy - aVec.y
                                                distance = math.sqrt(dx*dx + dy*dy)
                                            end
                                        end

                                        if distance < closestDistance then
                                            closestDistance = distance
                                            closestAirbase = airbase
                                            closestSquadron = squadron
                                        end
                                    end
                                end

                                for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
                                    local airbase = AIRBASE:FindByName(squadron.airbaseName)
                                    if airbase then
                                        local distance = math.huge
                                        local okVec, aVec = pcall(function() return airbase:GetCoordinate():GetVec2() end)
                                        if okVec and aVec and type(aVec) == 'table' then
                                            local cx, cy
                                            if cargoCoord.x and cargoCoord.z then
                                                cx, cy = cargoCoord.x, cargoCoord.z
                                            elseif cargoCoord.x and cargoCoord.y then
                                                cx, cy = cargoCoord.x, cargoCoord.y
                                            elseif cargoCoord[1] and cargoCoord[3] then
                                                cx, cy = cargoCoord[1], cargoCoord[3]
                                            elseif cargoCoord[1] and cargoCoord[2] then
                                                cx, cy = cargoCoord[1], cargoCoord[2]
                                            end
                                            if cx and cy then
                                                local dx = cx - aVec.x
                                                local dy = cy - aVec.y
                                                distance = math.sqrt(dx*dx + dy*dy)
                                            end
                                        end

                                        if distance < closestDistance then
                                            closestDistance = distance
                                            closestAirbase = airbase
                                            closestSquadron = squadron
                                        end
                                    end
                                end

                                if closestAirbase and closestDistance < ADVANCED_SETTINGS.cargoLandingEventRadius then
                                    local abCoalition = closestAirbase:GetCoalition()
                                    local coalitionKey = (abCoalition == coalition.side.RED) and "red" or "blue"

                                    -- Ensure the raw group name actually looks like a cargo aircraft before crediting
                                    local rawNameUpCheck = tostring(rawName):upper()
                                    local isCargoProxy = false
                                    for _, pattern in pairs(ADVANCED_SETTINGS.cargoPatterns) do
                                        if string.find(rawNameUpCheck, pattern) then
                                            isCargoProxy = true
                                            break
                                        end
                                    end

                                    if not isCargoProxy then
                                        if ADVANCED_SETTINGS.verboseProxyLogging then
                                            log("LANDING IGNORED (raw-proxy): " .. tostring(rawName) .. " is not a cargo-type name, skipping delivery proxy", true)
                                        else
                                            log("LANDING IGNORED (raw-proxy): " .. tostring(rawName) .. " is not a cargo-type name, skipping delivery proxy", true)
                                        end
                                    else
                                        -- Build a small proxy object that exposes GetName and GetID so processCargoDelivery can use it
                                        local cargoProxy = {}
                                        function cargoProxy:GetName()
                                            local okn, nm = pcall(function()
                                                if rawGroup and rawGroup.getName then return rawGroup:getName() end
                                                return tostring(rawName)
                                            end)
                                            return (okn and nm) and tostring(nm) or tostring(rawName)
                                        end
                                        function cargoProxy:GetID()
                                            local okid, id = pcall(function()
                                                if rawGroup and rawGroup.getID then return rawGroup:getID() end
                                                if rawGroup and rawGroup.getID == nil and rawGroup.getController then
                                                    -- Try to hash name as fallback unique-ish id
                                                    return tostring(rawName) .. "_proxy"
                                                end
                                                return nil
                                            end)
                                            return (okid and id) and id or tostring(rawName) .. "_proxy"
                                        end

                                        if ADVANCED_SETTINGS.verboseProxyLogging then
                                            log("LANDING DELIVERY (raw-proxy): " .. tostring(rawName) .. " landed and delivered at " .. closestSquadron.airbaseName .. " (distance: " .. math.floor(closestDistance) .. "m) - using proxy object", true)
                                        end
                                        processCargoDelivery(cargoProxy, closestSquadron, abCoalition, coalitionKey)
                                    end
                                else
                                    if ADVANCED_SETTINGS.verboseProxyLogging then
                                        log("LANDING DETECTED (raw-proxy): " .. tostring(rawName) .. " landed but no valid airbase found within range (closest: " .. (closestDistance and math.floor(closestDistance) .. "m" or "none") .. ")", true)
                                    end
                                end
                            else
                                log("LANDING EVENT: Could not extract coordinate from raw DCS group for proxy processing: " .. tostring(rawName), true)
                            end
                        end
                    else
                        log("LANDING EVENT: rawGroup:getName() failed", true)
                    end
                else
                    log("LANDING EVENT: raw DCS group retrieval failed", true)
                end
            end
        end
    end
end

-- Reassign squadron to an alternative airbase when primary airbase has issues
local function reassignSquadronToAlternativeAirbase(squadron, coalitionKey)
    local coalitionSide = (coalitionKey == "red") and coalition.side.RED or coalition.side.BLUE
    local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
    local squadronConfig = getSquadronConfig(coalitionSide)
    
    -- Find alternative airbases (other squadrons' airbases that are operational)
    local alternativeAirbases = {}
    for _, altSquadron in pairs(squadronConfig) do
        if altSquadron.airbaseName ~= squadron.airbaseName then
            local usable, status = isAirbaseUsable(altSquadron.airbaseName, coalitionSide)
            local healthStatus = airbaseHealthStatus[coalitionKey][altSquadron.airbaseName] or "operational"
            
            if usable and healthStatus == "operational" then
                table.insert(alternativeAirbases, altSquadron.airbaseName)
            end
        end
    end
    
    if #alternativeAirbases > 0 then
        -- Select random alternative airbase
        local newAirbase = alternativeAirbases[math.random(1, #alternativeAirbases)]
        
        -- Update squadron configuration (this is a runtime change)
        squadron.airbaseName = newAirbase
        airbaseHealthStatus[coalitionKey][squadron.airbaseName] = "operational" -- Reset health for new assignment
        
        log("REASSIGNED: " .. coalitionName .. " Squadron " .. squadron.displayName .. " moved from " .. squadron.airbaseName .. " to " .. newAirbase)
        MESSAGE:New(coalitionName .. " Squadron " .. squadron.displayName .. " reassigned to " .. newAirbase .. " due to airbase issues", 20):ToCoalition(coalitionSide)
    else
        log("WARNING: No alternative airbases available for " .. coalitionName .. " Squadron " .. squadron.displayName)
        MESSAGE:New("WARNING: No alternative airbases available for " .. squadron.displayName, 30):ToCoalition(coalitionSide)
    end
end

-- Monitor for stuck aircraft at airbases
local function monitorStuckAircraft()
    local currentTime = timer.getTime()
    local stuckThreshold = 300 -- 5 minutes before considering aircraft stuck
    local movementThreshold = 50 -- meters - aircraft must move at least this far to not be considered stuck
    
    for _, coalitionKey in ipairs({"red", "blue"}) do
        local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
        
        for aircraftName, trackingData in pairs(aircraftSpawnTracking[coalitionKey]) do
            if trackingData and trackingData.group and trackingData.group:IsAlive() then
                local timeSinceSpawn = currentTime - trackingData.spawnTime
                
                -- Only check aircraft that have been spawned for at least the threshold time
                if timeSinceSpawn >= stuckThreshold then
                    local currentPos = safeCoordinate(trackingData.group)
                    local spawnPos = trackingData.spawnPos
                    local distanceMoved = nil

                    if currentPos and spawnPos and type(spawnPos) == "table" and spawnPos.Get2DDistance then
                        local okDist, dist = pcall(function() return spawnPos:Get2DDistance(currentPos) end)
                        if okDist and dist then
                            distanceMoved = dist
                        end
                    end

                    if distanceMoved then
                        
                        -- Check if aircraft has moved less than threshold (stuck)
                        if distanceMoved < movementThreshold then
                            log("STUCK AIRCRAFT DETECTED: " .. aircraftName .. " at " .. trackingData.airbase .. 
                                " has only moved " .. math.floor(distanceMoved) .. "m in " .. math.floor(timeSinceSpawn/60) .. " minutes")
                            
                            -- Mark airbase as having stuck aircraft
                            airbaseHealthStatus[coalitionKey][trackingData.airbase] = "stuck-aircraft"
                            
                            -- Remove the stuck aircraft and clear tracking
                            pcall(function() trackingData.group:Destroy() end)
                            cleanupInterceptorEntry(aircraftName, coalitionKey)
                            
                            -- Reassign squadron to alternative airbase
                            reassignSquadronToAlternativeAirbase(trackingData.squadron, coalitionKey)
                            
                            MESSAGE:New(coalitionName .. " aircraft stuck at " .. trackingData.airbase .. " - destroyed and squadron reassigned", 15):ToCoalition(coalitionKey == "red" and coalition.side.RED or coalition.side.BLUE)
                        else
                            -- Aircraft has moved sufficiently, remove from tracking (no longer needs monitoring)
                            log("Aircraft " .. aircraftName .. " has moved " .. math.floor(distanceMoved) .. "m - removing from stuck monitoring", true)
                            aircraftSpawnTracking[coalitionKey][aircraftName] = nil
                        end
                    else
                        log("Stuck monitor: no coordinate data for " .. aircraftName .. "; removing from tracking", true)
                        aircraftSpawnTracking[coalitionKey][aircraftName] = nil
                    end
                end
            else
                -- Clean up dead aircraft from tracking
                aircraftSpawnTracking[coalitionKey][aircraftName] = nil
            end
        end
    end
end

-- Send interceptor back to base
local function sendInterceptorHome(interceptor, coalitionSide)
    if not interceptor or not interceptor:IsAlive() then
        return
    end
    
    -- Find nearest friendly airbase
    local interceptorCoord = safeCoordinate(interceptor)
    if not interceptorCoord then
        log("ERROR: Could not get interceptor coordinates for RTB", true)
        return
    end
    local nearestAirbase = nil
    local nearestAirbaseCoord = nil
    local shortestDistance = math.huge
    local squadronConfig = getSquadronConfig(coalitionSide)
    
    -- Check all squadron airbases to find the nearest one that's still friendly
    for _, squadron in pairs(squadronConfig) do
        local airbase = AIRBASE:FindByName(squadron.airbaseName)
        if airbase and airbase:GetCoalition() == coalitionSide and airbase:IsAlive() then
            local airbaseCoord = safeCoordinate(airbase)
            if airbaseCoord then
                local okDist, distance = pcall(function() return interceptorCoord:Get2DDistance(airbaseCoord) end)
                if okDist and distance and distance < shortestDistance then
                    shortestDistance = distance
                    nearestAirbase = airbase
                    nearestAirbaseCoord = airbaseCoord
                end
            end
        end
    end
    
    if nearestAirbase and nearestAirbaseCoord then
        local airbaseName = "airbase"
        local okABName, fetchedABName = pcall(function() return nearestAirbase:GetName() end)
        if okABName and fetchedABName then
            airbaseName = fetchedABName
        end

        local rtbAltitude = ADVANCED_SETTINGS.rtbAltitude * 0.3048 -- Convert feet to meters
        local okRtb, rtbCoord = pcall(function() return nearestAirbaseCoord:SetAltitude(rtbAltitude) end)
        if not okRtb or not rtbCoord then
            log("ERROR: Failed to compute RTB coordinate for " .. airbaseName, true)
            return
        end
        
        -- Clear current tasks and route home
        pcall(function() interceptor:ClearTasks() end)
        local routeOk, routeErr = pcall(function() interceptor:RouteAirTo(rtbCoord, ADVANCED_SETTINGS.rtbSpeed * 0.5144, "BARO") end)
        
        local _, coalitionName = getCoalitionSettings(coalitionSide)
        local interceptorName = "interceptor"
        local okName, fetchedName = pcall(function() return interceptor:GetName() end)
        if okName and fetchedName then
            interceptorName = fetchedName
        end

        if not routeOk and routeErr then
            log("ERROR: Failed to assign RTB route for " .. interceptorName .. " -> " .. airbaseName .. ": " .. tostring(routeErr), true)
        else
            log("Sending " .. coalitionName .. " " .. interceptorName .. " back to " .. airbaseName, true)
        end
        
        -- Schedule cleanup after they should have landed
        local coalitionSettings = getCoalitionSettings(coalitionSide)
        local flightTime = math.ceil(shortestDistance / (ADVANCED_SETTINGS.rtbSpeed * 0.5144)) + coalitionSettings.rtbFlightBuffer
        
        SCHEDULER:New(nil, function()
            local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
            local name = nil
            if interceptor and interceptor.GetName then
                local ok, value = pcall(function() return interceptor:GetName() end)
                if ok then name = value end
            end
            if name and activeInterceptors[coalitionKey][name] then
                destroyInterceptorGroup(interceptor, coalitionKey, 0)
                log("Cleaned up " .. coalitionName .. " " .. name .. " after RTB", true)
            end
        end, {}, flightTime)
    else
        local _, coalitionName = getCoalitionSettings(coalitionSide)
        log("No friendly airbase found for " .. coalitionName .. " " .. interceptor:GetName() .. ", will clean up normally")
    end
end

-- Check if airbase is still usable
local function isAirbaseUsable(airbaseName, expectedCoalition)
    local airbase = AIRBASE:FindByName(airbaseName)
    if not airbase then
        return false, "not found"
    elseif airbase:GetCoalition() ~= expectedCoalition then
        local capturedBy = "Unknown"
        if airbase:GetCoalition() == coalition.side.RED then
            capturedBy = "Red"
        elseif airbase:GetCoalition() == coalition.side.BLUE then
            capturedBy = "Blue"
        else
            capturedBy = "Neutral"
        end
        return false, "captured by " .. capturedBy
    elseif not airbase:IsAlive() then
        return false, "destroyed"
    else
        return true, "operational"
    end
end

-- Count active fighters for coalition
local function countActiveFighters(coalitionSide)
    local count = 0
    local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
    
    for _, interceptorData in pairs(activeInterceptors[coalitionKey]) do
        if interceptorData and interceptorData.group and interceptorData.group:IsAlive() then
            count = count + interceptorData.group:GetSize()
        end
    end
    return count
end

-- Find best squadron to launch for coalition using zone-based priorities
local function findBestSquadron(threatCoord, threatSize, coalitionSide)
    local bestSquadron = nil
    local bestPriority = "none"
    local bestResponseRatio = 0
    local shortestDistance = math.huge
    local currentTime = timer.getTime()
    local squadronConfig = getSquadronConfig(coalitionSide)
    local coalitionSettings, coalitionName = getCoalitionSettings(coalitionSide)
    local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
    local zonePriorityOrder = {"tertiary", "primary", "secondary", "global"}
    
    -- First pass: find squadrons that can respond to this threat
    local availableSquadrons = {}
    
    for _, squadron in pairs(squadronConfig) do
        -- Check basic availability
        local squadronAvailable = true
        local unavailableReason = ""
        
        -- Check squadron state first
        if squadron.state and squadron.state ~= "operational" then
            squadronAvailable = false
            if squadron.state == "captured" then
                unavailableReason = "airbase captured by enemy"
            elseif squadron.state == "destroyed" then
                unavailableReason = "airbase destroyed"
            else
                unavailableReason = "squadron not operational (state: " .. tostring(squadron.state) .. ")"
            end
        end
        
        -- Check cooldown
        if squadronAvailable and squadronCooldowns[coalitionKey][squadron.templateName] then
            local cooldownEnd = squadronCooldowns[coalitionKey][squadron.templateName]
            if currentTime < cooldownEnd then
                local timeLeft = math.ceil((cooldownEnd - currentTime) / 60)
                squadronAvailable = false
                unavailableReason = "on cooldown for " .. timeLeft .. "m"
            else
                -- Cooldown expired, remove it
                squadronCooldowns[coalitionKey][squadron.templateName] = nil
                log(coalitionName .. " Squadron " .. squadron.displayName .. " cooldown expired, available for launch", true)
            end
        end
        
        -- Check aircraft availability
        if squadronAvailable then
            local availableAircraft = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0
            if availableAircraft <= 0 then
                squadronAvailable = false
                unavailableReason = "no aircraft available (" .. availableAircraft .. "/" .. squadron.aircraft .. ")"
            end
        end
        
        -- Check airbase status
        if squadronAvailable then
            local airbase = AIRBASE:FindByName(squadron.airbaseName)
            if not airbase then
                squadronAvailable = false
                unavailableReason = "airbase not found"
            elseif airbase:GetCoalition() ~= coalitionSide then
                squadronAvailable = false
                unavailableReason = "airbase no longer under " .. coalitionName .. " control"
            elseif not airbase:IsAlive() then
                squadronAvailable = false
                unavailableReason = "airbase destroyed"
            end
        end
        
        -- Check template exists (Note: Templates are validated during SPAWN:New() call)
        -- Template validation is handled by MOOSE SPAWN class during actual spawning
        
        if squadronAvailable then
            -- Get zone priority and response ratio
            local zonePriority, responseRatio, zoneDescription = getThreatZonePriority(threatCoord, squadron, coalitionSide)
            
            -- Check if threat meets priority threshold for secondary zones
            local zoneConfig = squadron.zoneConfig or getDefaultZoneConfig()
            if zonePriority == "secondary" and zoneConfig.ignoreLowPriority then
                if threatSize < zoneConfig.priorityThreshold then
                    log(coalitionName .. " " .. squadron.displayName .. " ignoring low-priority threat in secondary zone (" .. 
                        threatSize .. " < " .. zoneConfig.priorityThreshold .. ")", true)
                    responseRatio = 0
                    zonePriority = "none"
                end
            end
            
            if responseRatio > 0 then
                local airbase = AIRBASE:FindByName(squadron.airbaseName)
                local airbaseCoord = airbase:GetCoordinate()
                local distance = airbaseCoord:Get2DDistance(threatCoord)
                
                table.insert(availableSquadrons, {
                    squadron = squadron,
                    zonePriority = zonePriority,
                    responseRatio = responseRatio,
                    distance = distance,
                    zoneDescription = zoneDescription
                })
                
                log(coalitionName .. " " .. squadron.displayName .. " can respond: " .. zoneDescription .. 
                    " (ratio: " .. responseRatio .. ", distance: " .. math.floor(distance/1852) .. "nm)", true)
            else
                log(coalitionName .. " " .. squadron.displayName .. " will not respond: " .. zoneDescription, true)
            end
        else
            log(coalitionName .. " " .. squadron.displayName .. " unavailable: " .. unavailableReason, true)
        end
    end
    
    -- Second pass: select best squadron by priority and distance
    if #availableSquadrons > 0 then
        -- Sort by zone priority (higher priority first), then by distance (closer first)
        table.sort(availableSquadrons, function(a, b)
            -- Get priority indices
            local aPriorityIndex = 5
            local bPriorityIndex = 5
            for i, priority in ipairs(zonePriorityOrder) do
                if a.zonePriority == priority then aPriorityIndex = i end
                if b.zonePriority == priority then bPriorityIndex = i end
            end
            
            -- First sort by priority (lower index = higher priority)
            if aPriorityIndex ~= bPriorityIndex then
                return aPriorityIndex < bPriorityIndex
            end
            
            -- Then sort by distance (closer is better)
            return a.distance < b.distance
        end)
        
        local selected = availableSquadrons[1]
        log("Selected " .. coalitionName .. " " .. selected.squadron.displayName .. " for response: " .. 
            selected.zoneDescription .. " (distance: " .. math.floor(selected.distance/1852) .. "nm)")
        
        return selected.squadron, selected.responseRatio, selected.zoneDescription
    end
    
    if ADVANCED_SETTINGS.enableDetailedLogging then
        log("No " .. coalitionName .. " squadron available for threat at coordinates")
    end
    return nil, 0, "no available squadrons"
end

-- Launch interceptor for coalition
local function launchInterceptor(threatGroup, coalitionSide)
    if not threatGroup or not threatGroup:IsAlive() then
        return
    end
    
    local threatCoord = threatGroup:GetCoordinate()
    local threatName = threatGroup:GetName()
    local threatSize = threatGroup:GetSize()
    local coalitionSettings, coalitionName = getCoalitionSettings(coalitionSide)
    local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
    
    -- Check if threat already has interceptors assigned
    if assignedThreats[coalitionKey][threatName] then
        local assignedInterceptors = assignedThreats[coalitionKey][threatName]
        local aliveCount = 0
        
        -- Check if assigned interceptors are still alive
        if type(assignedInterceptors) == "table" then
            for _, interceptor in pairs(assignedInterceptors) do
                if interceptor and interceptor:IsAlive() then
                    aliveCount = aliveCount + 1
                end
            end
        else
            -- Handle legacy single interceptor assignment
            if assignedInterceptors and assignedInterceptors:IsAlive() then
                aliveCount = 1
            end
        end
        
        if aliveCount > 0 then
            return -- Still being intercepted
        else
            -- All interceptors are dead, clear the assignment
            assignedThreats[coalitionKey][threatName] = nil
        end
    end
    
    -- Find best squadron using zone-based priority system first
    local squadron, zoneResponseRatio, zoneDescription = findBestSquadron(threatCoord, threatSize, coalitionSide)
    
    if not squadron then
        if ADVANCED_SETTINGS.enableDetailedLogging then
            log("No " .. coalitionName .. " squadron available")
        end
        return
    end
    
    -- Calculate how many interceptors to launch using zone-modified ratio
    local finalInterceptRatio = coalitionSettings.interceptRatio * zoneResponseRatio
    local interceptorsNeeded = math.max(1, math.ceil(threatSize * finalInterceptRatio))
    
    -- Check if we have capacity
    if countActiveFighters(coalitionSide) + interceptorsNeeded > coalitionSettings.maxActiveCAP then
        interceptorsNeeded = coalitionSettings.maxActiveCAP - countActiveFighters(coalitionSide)
        if interceptorsNeeded <= 0 then
            log(coalitionName .. " max fighters airborne, skipping launch")
            return
        end
    end
    if not squadron then
        if ADVANCED_SETTINGS.enableDetailedLogging then
            log("No " .. coalitionName .. " squadron available")
        end
        return
    end
    
    -- Limit interceptors to available aircraft
    local availableAircraft = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0
    interceptorsNeeded = math.min(interceptorsNeeded, availableAircraft)
    
    if interceptorsNeeded <= 0 then
        log(coalitionName .. " Squadron " .. squadron.displayName .. " has no aircraft to launch")
        return
    end
    
    -- Launch multiple interceptors to match threat
    local spawn = SPAWN:New(squadron.templateName)
    if not spawn then
        log("ERROR: Failed to create SPAWN object for " .. coalitionName .. " " .. squadron.templateName)
        return
    end
    spawn:InitCleanUp(900)
    
    local interceptors = {}
    
    for i = 1, interceptorsNeeded do
        local interceptor = spawn:Spawn()
        
        if interceptor then
            table.insert(interceptors, interceptor)
            
            -- Wait a moment for initialization
            SCHEDULER:New(nil, function()
                if interceptor and interceptor:IsAlive() then
                    -- Set aggressive AI
                    interceptor:OptionROEOpenFire()
                    interceptor:OptionROTVertical()
                    
                    -- Route to threat
                    local currentThreatCoord = safeCoordinate(threatGroup)
                    if currentThreatCoord then
                        local okIntercept, interceptCoord = pcall(function()
                            return currentThreatCoord:SetAltitude(squadron.altitude * 0.3048)
                        end)
                        if okIntercept and interceptCoord then
                            pcall(function()
                                interceptor:RouteAirTo(interceptCoord, squadron.speed * 0.5144, "BARO")
                            end)
                        end
                        
                        -- Attack the threat
                        local attackTask = {
                            id = 'AttackGroup',
                            params = {
                                groupId = threatGroup:GetID(),
                                weaponType = 'Auto',
                                attackQtyLimit = 0,
                                priority = 1
                            }
                        }
                        interceptor:PushTask(attackTask, 1)
                    end
                end
            end, {}, 3)
            
            -- Track the interceptor with squadron info
            local interceptorName = "interceptor"
            local okName, fetchedName = pcall(function() return interceptor:GetName() end)
            if okName and fetchedName then
                interceptorName = fetchedName
            end

            activeInterceptors[coalitionKey][interceptorName] = {
                group = interceptor,
                squadron = squadron.templateName,
                displayName = squadron.displayName
            }
            
            -- Track spawn position for stuck aircraft detection
            local spawnPos = safeCoordinate(interceptor)
            if spawnPos then
                aircraftSpawnTracking[coalitionKey][interceptorName] = {
                    spawnPos = spawnPos,
                    spawnTime = timer.getTime(),
                    squadron = squadron,
                    airbase = squadron.airbaseName
                }
                log("Tracking spawn position for " .. interceptorName .. " at " .. squadron.airbaseName, true)
            end
            
            -- Emergency cleanup (safety net)
            SCHEDULER:New(nil, function()
                local name = nil
                if interceptor and interceptor.GetName then
                    local ok, value = pcall(function() return interceptor:GetName() end)
                    if ok then name = value end
                end
                if name and activeInterceptors[coalitionKey][name] then
                    log("Emergency cleanup of " .. coalitionName .. " " .. name .. " (should have RTB'd)")
                    destroyInterceptorGroup(interceptor, coalitionKey, 0)
                end
            end, {}, coalitionSettings.emergencyCleanupTime)
        end
    end
    
    -- Log the launch and track assignment
    if #interceptors > 0 then
        -- Decrement squadron aircraft count
        local currentCount = squadronAircraftCounts[coalitionKey][squadron.templateName] or 0
        squadronAircraftCounts[coalitionKey][squadron.templateName] = math.max(0, currentCount - #interceptors)
        local remainingCount = squadronAircraftCounts[coalitionKey][squadron.templateName]
        
        log("Launched " .. #interceptors .. " x " .. coalitionName .. " " .. squadron.displayName .. " to intercept " .. 
            threatSize .. " x " .. threatName .. " (" .. zoneDescription .. ", ratio: " .. string.format("%.1f", finalInterceptRatio) .. 
            ", remaining: " .. remainingCount .. "/" .. squadron.aircraft .. ")")
        assignedThreats[coalitionKey][threatName] = interceptors
        lastLaunchTime[coalitionKey][threatName] = timer.getTime()
        
        -- Apply cooldown immediately when squadron launches
        local currentTime = timer.getTime()
        squadronCooldowns[coalitionKey][squadron.templateName] = currentTime + coalitionSettings.squadronCooldown
        local cooldownMinutes = coalitionSettings.squadronCooldown / 60
        log(coalitionName .. " Squadron " .. squadron.displayName .. " LAUNCHED! Applying " .. cooldownMinutes .. " minute cooldown")
    end
end

-- Main threat detection loop for coalition
local function detectThreatsForCoalition(coalitionSide)
    local coalitionSettings, coalitionName = getCoalitionSettings(coalitionSide)
    local enemyCoalition = (coalitionSide == coalition.side.RED) and "blue" or "red"
    local coalitionKey = (coalitionSide == coalition.side.RED) and "red" or "blue"
    
    log("Scanning for " .. coalitionName .. " threats...", true)
    
    -- Clean up dead threats from tracking
    local currentThreats = {}
    
    -- Find all enemy aircraft using cached set for performance
    local cacheKey = enemyCoalition .. "Aircraft"
    if not cachedSets[cacheKey] then
        cachedSets[cacheKey] = SET_GROUP:New():FilterCoalitions(enemyCoalition):FilterCategoryAirplane():FilterStart()
    end
    local enemyAircraft = cachedSets[cacheKey]
    local threatCount = 0
    
    enemyAircraft:ForEach(function(enemyGroup)
        if enemyGroup and enemyGroup:IsAlive() then
            threatCount = threatCount + 1
            currentThreats[enemyGroup:GetName()] = true
            log("Found " .. coalitionName .. " threat: " .. enemyGroup:GetName() .. " (" .. enemyGroup:GetTypeName() .. ")", true)
            
            -- Launch interceptor for this threat
            launchInterceptor(enemyGroup, coalitionSide)
        end
    end)
    
    -- Clean up assignments for threats that no longer exist and send interceptors home
    for threatName, assignedInterceptors in pairs(assignedThreats[coalitionKey]) do
        if not currentThreats[threatName] then
            log("Threat " .. threatName .. " eliminated, sending " .. coalitionName .. " interceptors home...")
            
            -- Send assigned interceptors back to base
            if type(assignedInterceptors) == "table" then
                for _, interceptor in pairs(assignedInterceptors) do
                    if interceptor and interceptor:IsAlive() then
                        sendInterceptorHome(interceptor, coalitionSide)
                    end
                end
            else
                -- Handle legacy single interceptor assignment
                if assignedInterceptors and assignedInterceptors:IsAlive() then
                    sendInterceptorHome(assignedInterceptors, coalitionSide)
                end
            end
            
            assignedThreats[coalitionKey][threatName] = nil
        end
    end
    
    -- Count assigned threats
    local assignedCount = 0
    for _ in pairs(assignedThreats[coalitionKey]) do assignedCount = assignedCount + 1 end
    
    log(coalitionName .. " scan complete: " .. threatCount .. " threats, " .. countActiveFighters(coalitionSide) .. " active fighters, " .. 
        assignedCount .. " assigned")
end

-- Main threat detection loop - calls both coalitions
local function detectThreats()
    if TADC_SETTINGS.enableRed then
        detectThreatsForCoalition(coalition.side.RED)
    end
    
    if TADC_SETTINGS.enableBlue then
        detectThreatsForCoalition(coalition.side.BLUE)
    end
end

-- Monitor interceptor groups for cleanup when destroyed
local function monitorInterceptors()
    -- Check RED interceptors
    if TADC_SETTINGS.enableRed then
        for interceptorName, interceptorData in pairs(activeInterceptors.red) do
            if interceptorData and interceptorData.group then
                if not interceptorData.group:IsAlive() then
                    -- Interceptor group is destroyed - just clean up tracking
                    local displayName = interceptorData.displayName
                    log("RED Interceptor from " .. displayName .. " destroyed: " .. interceptorName, true)
                    
                    -- Remove from active tracking
                    activeInterceptors.red[interceptorName] = nil
                end
            end
        end
    end
    
    -- Check BLUE interceptors
    if TADC_SETTINGS.enableBlue then
        for interceptorName, interceptorData in pairs(activeInterceptors.blue) do
            if interceptorData and interceptorData.group then
                if not interceptorData.group:IsAlive() then
                    -- Interceptor group is destroyed - just clean up tracking
                    local displayName = interceptorData.displayName
                    log("BLUE Interceptor from " .. displayName .. " destroyed: " .. interceptorName, true)
                    
                    -- Remove from active tracking
                    activeInterceptors.blue[interceptorName] = nil
                end
            end
        end
    end
end

-- Periodic airbase status check
local function checkAirbaseStatus()
    log("=== AIRBASE STATUS REPORT ===")
    
    local redUsableCount = 0
    local blueUsableCount = 0
    local currentTime = timer.getTime()
    
    -- Check RED airbases
    if TADC_SETTINGS.enableRed then
        log("=== RED COALITION STATUS ===")
        for _, squadron in pairs(RED_SQUADRON_CONFIG) do
            local airbase = AIRBASE:FindByName(squadron.airbaseName)
            local aircraftCount = squadronAircraftCounts.red[squadron.templateName] or 0
            local maxAircraft = squadron.aircraft
            
            -- Determine status based on squadron state
            local statusPrefix = "✗"
            local statusText = ""
            local usable = false
            
            if squadron.state == "operational" then
                statusPrefix = "✓"
                statusText = "Operational: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
                usable = true
            elseif squadron.state == "captured" then
                -- Determine who captured it
                local capturedBy = "enemy"
                if airbase and airbase:IsAlive() then
                    local airbaseCoalition = airbase:GetCoalition()
                    if airbaseCoalition == coalition.side.BLUE then
                        capturedBy = "Blue"
                    elseif airbaseCoalition == coalition.side.NEUTRAL then
                        capturedBy = "neutral forces"
                    end
                end
                statusText = "Captured by " .. capturedBy .. ": " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
            elseif squadron.state == "destroyed" then
                statusText = "Destroyed: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
            else
                statusText = "Unknown state: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
            end
            
            -- Add zone information if configured
            local zoneStatus = ""
            if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then
                local zones = {}
                if squadron.primaryZone then table.insert(zones, "P:" .. squadron.primaryZone) end
                if squadron.secondaryZone then table.insert(zones, "S:" .. squadron.secondaryZone) end
                if squadron.tertiaryZone then table.insert(zones, "T:" .. squadron.tertiaryZone) end
                zoneStatus = " Zones: " .. table.concat(zones, " ")
            end
            
            -- Check if squadron is on cooldown (only show for operational squadrons)
            local cooldownStatus = ""
            if squadron.state == "operational" and squadronCooldowns.red[squadron.templateName] then
                local cooldownEnd = squadronCooldowns.red[squadron.templateName]
                if currentTime < cooldownEnd then
                    local timeLeft = math.ceil((cooldownEnd - currentTime) / 60)
                    cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)"
                end
            end
            
            local fullStatus = statusText .. zoneStatus .. cooldownStatus
            
            if usable and cooldownStatus == "" and aircraftCount > 0 then
                redUsableCount = redUsableCount + 1
            end
            
            log(statusPrefix .. " " .. squadron.displayName .. " (" .. squadron.airbaseName .. ") - " .. fullStatus)
        end
        log("RED Status: " .. redUsableCount .. "/" .. #RED_SQUADRON_CONFIG .. " airbases operational")
    end
    
    -- Check BLUE airbases
    if TADC_SETTINGS.enableBlue then
        log("=== BLUE COALITION STATUS ===")
        for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
            local airbase = AIRBASE:FindByName(squadron.airbaseName)
            local aircraftCount = squadronAircraftCounts.blue[squadron.templateName] or 0
            local maxAircraft = squadron.aircraft
            
            -- Determine status based on squadron state
            local statusPrefix = "✗"
            local statusText = ""
            local usable = false
            
            if squadron.state == "operational" then
                statusPrefix = "✓"
                statusText = "Operational: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
                usable = true
            elseif squadron.state == "captured" then
                -- Determine who captured it
                local capturedBy = "enemy"
                if airbase and airbase:IsAlive() then
                    local airbaseCoalition = airbase:GetCoalition()
                    if airbaseCoalition == coalition.side.RED then
                        capturedBy = "Red"
                    elseif airbaseCoalition == coalition.side.NEUTRAL then
                        capturedBy = "neutral forces"
                    end
                end
                statusText = "Captured by " .. capturedBy .. ": " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
            elseif squadron.state == "destroyed" then
                statusText = "Destroyed: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
            else
                statusText = "Unknown state: " .. aircraftCount .. "/" .. maxAircraft .. " aircraft"
            end
            
            -- Add zone information if configured
            local zoneStatus = ""
            if squadron.primaryZone or squadron.secondaryZone or squadron.tertiaryZone then
                local zones = {}
                if squadron.primaryZone then table.insert(zones, "P:" .. squadron.primaryZone) end
                if squadron.secondaryZone then table.insert(zones, "S:" .. squadron.secondaryZone) end
                if squadron.tertiaryZone then table.insert(zones, "T:" .. squadron.tertiaryZone) end
                zoneStatus = " Zones: " .. table.concat(zones, " ")
            end
            
            -- Check if squadron is on cooldown (only show for operational squadrons)
            local cooldownStatus = ""
            if squadron.state == "operational" and squadronCooldowns.blue[squadron.templateName] then
                local cooldownEnd = squadronCooldowns.blue[squadron.templateName]
                if currentTime < cooldownEnd then
                    local timeLeft = math.ceil((cooldownEnd - currentTime) / 60)
                    cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)"
                end
            end
            
            local fullStatus = statusText .. zoneStatus .. cooldownStatus
            
            if usable and cooldownStatus == "" and aircraftCount > 0 then
                blueUsableCount = blueUsableCount + 1
            end
            
            log(statusPrefix .. " " .. squadron.displayName .. " (" .. squadron.airbaseName .. ") - " .. fullStatus)
        end
        log("BLUE Status: " .. blueUsableCount .. "/" .. #BLUE_SQUADRON_CONFIG .. " airbases operational")
    end
end

-- Cleanup old delivery records to prevent memory buildup
local function cleanupOldDeliveries()
    if _G.processedDeliveries then
        local currentTime = timer.getTime()
        local cleanupAge = 3600 -- Remove delivery records older than 1 hour
        local removedCount = 0
        
        for deliveryKey, timestamp in pairs(_G.processedDeliveries) do
            if currentTime - timestamp > cleanupAge then
                _G.processedDeliveries[deliveryKey] = nil
                removedCount = removedCount + 1
            end
        end
        
        if removedCount > 0 then
            log("Cleaned up " .. removedCount .. " old cargo delivery records", true)
        end
    end
end

-- Update squadron states based on airbase coalition control
local function updateSquadronStates()
    -- Update RED squadrons
    for _, squadron in pairs(RED_SQUADRON_CONFIG) do
        local airbase = AIRBASE:FindByName(squadron.airbaseName)
        if airbase and airbase:IsAlive() then
            local airbaseCoalition = airbase:GetCoalition()
            if airbaseCoalition == coalition.side.RED then
                -- Only update to operational if not already operational (avoid spam)
                if squadron.state ~= "operational" then
                    squadron.state = "operational"
                    log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " is now operational")
                end
            else
                -- Airbase captured
                if squadron.state ~= "captured" then
                    squadron.state = "captured"
                    log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " has been captured by enemy")
                end
            end
        else
            -- Airbase destroyed or not found
            if squadron.state ~= "destroyed" then
                squadron.state = "destroyed"
                log("RED Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " airbase destroyed or not found")
            end
        end
    end
    
    -- Update BLUE squadrons
    for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
        local airbase = AIRBASE:FindByName(squadron.airbaseName)
        if airbase and airbase:IsAlive() then
            local airbaseCoalition = airbase:GetCoalition()
            if airbaseCoalition == coalition.side.BLUE then
                -- Only update to operational if not already operational (avoid spam)
                if squadron.state ~= "operational" then
                    squadron.state = "operational"
                    log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " is now operational")
                end
            else
                -- Airbase captured
                if squadron.state ~= "captured" then
                    squadron.state = "captured"
                    log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " has been captured by enemy")
                end
            end
        else
            -- Airbase destroyed or not found
            if squadron.state ~= "destroyed" then
                squadron.state = "destroyed"
                log("BLUE Squadron " .. squadron.displayName .. " at " .. squadron.airbaseName .. " airbase destroyed or not found")
            end
        end
    end
end

-- System initialization
local function initializeSystem()
    log("Universal Dual-Coalition TADC starting...")
    
    -- Create zones from late-activated helicopter units (MOOSE method)
    -- This allows using helicopters named "RED_BORDER", "BLUE_BORDER" etc. as zone markers
    -- Uses the helicopter's waypoints as polygon vertices (standard MOOSE method)
    local function createZoneFromUnit(unitName)
        -- Try to find as a group first (this is the standard MOOSE way)
        local group = GROUP:FindByName(unitName)
        if group then
            -- Create polygon zone using the group's waypoints as vertices
            local zone = ZONE_POLYGON:NewFromGroupName(unitName, unitName)
            if zone then
                log("Created polygon zone '" .. unitName .. "' from helicopter waypoints")
                return zone
            else
                log("Warning: Could not create polygon zone from group '" .. unitName .. "' - check waypoints")
            end
        else
            log("Warning: No group named '" .. unitName .. "' found for zone creation")
        end
        return nil
    end
    
    -- Try to create zones for all configured zone names
    local zoneNames = {}
    for _, squadron in pairs(RED_SQUADRON_CONFIG) do
        if squadron.primaryZone then table.insert(zoneNames, squadron.primaryZone) end
        if squadron.secondaryZone then table.insert(zoneNames, squadron.secondaryZone) end
        if squadron.tertiaryZone then table.insert(zoneNames, squadron.tertiaryZone) end
    end
    for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
        if squadron.primaryZone then table.insert(zoneNames, squadron.primaryZone) end
        if squadron.secondaryZone then table.insert(zoneNames, squadron.secondaryZone) end
        if squadron.tertiaryZone then table.insert(zoneNames, squadron.tertiaryZone) end
    end
    
    -- Create zones from helicopters
    for _, zoneName in ipairs(zoneNames) do
        if not ZONE:FindByName(zoneName) then
            createZoneFromUnit(zoneName)
        end
    end
    
    -- Validate configuration
    if not validateConfiguration() then
        log("System startup aborted due to configuration errors!")
        return false
    end
    
    -- Initialize squadron states
    for _, squadron in pairs(RED_SQUADRON_CONFIG) do
        squadron.state = "operational"
    end
    for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
        squadron.state = "operational"
    end
    log("Squadron states initialized")
    
    -- Log enabled coalitions
    local enabledCoalitions = {}
    if TADC_SETTINGS.enableRed then
        table.insert(enabledCoalitions, "RED (" .. #RED_SQUADRON_CONFIG .. " squadrons)")
    end
    if TADC_SETTINGS.enableBlue then
        table.insert(enabledCoalitions, "BLUE (" .. #BLUE_SQUADRON_CONFIG .. " squadrons)")
    end
    log("Enabled coalitions: " .. table.concat(enabledCoalitions, ", "))
    
    -- Log initial squadron aircraft counts
    if TADC_SETTINGS.enableRed then
        for _, squadron in pairs(RED_SQUADRON_CONFIG) do
            local count = squadronAircraftCounts.red[squadron.templateName]
            log("Initial RED: " .. squadron.displayName .. " has " .. count .. "/" .. squadron.aircraft .. " aircraft")
        end
    end
    
    if TADC_SETTINGS.enableBlue then
        for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
            local count = squadronAircraftCounts.blue[squadron.templateName]
            log("Initial BLUE: " .. squadron.displayName .. " has " .. count .. "/" .. squadron.aircraft .. " aircraft")
        end
    end
    
    -- Start schedulers
    -- Set up event handler for cargo landing detection (handled via MOOSE EVENTHANDLER wrapper below)

    -- Re-register world event handler for robust detection (handles raw DCS initiators and race cases)
    world.addEventHandler(cargoEventHandler)

    -- MOOSE-style EVENTHANDLER wrapper for readability: logs EventData but does NOT delegate to avoid double-processing
    if EVENTHANDLER then
        local TADC_CARGO_LANDING_HANDLER = EVENTHANDLER:New()
        function TADC_CARGO_LANDING_HANDLER:OnEventLand(EventData)
            -- Convert MOOSE EventData to raw world.event format and reuse existing handler logic
            if ADVANCED_SETTINGS.enableDetailedLogging then
                -- Log presence and types of key fields
                local function safeName(obj)
                    if not obj then return "<nil>" end
                    local ok, n = pcall(function()
                        if obj.GetName then return obj:GetName() end
                        if obj.getName then return obj:getName() end
                        return nil
                    end)
                    return (ok and n) and tostring(n) or "<unavailable>"
                end

                local iniUnitPresent = EventData.IniUnit ~= nil
                local iniGroupPresent = EventData.IniGroup ~= nil
                local placePresent = EventData.Place ~= nil
                local iniUnitName = safeName(EventData.IniUnit)
                local iniGroupName = safeName(EventData.IniGroup)
                local placeName = safeName(EventData.Place)

                log("MOOSE LAND EVENT: IniUnitPresent=" .. tostring(iniUnitPresent) .. ", IniUnitName=" .. tostring(iniUnitName) .. ", IniGroupPresent=" .. tostring(iniGroupPresent) .. ", IniGroupName=" .. tostring(iniGroupName) .. ", PlacePresent=" .. tostring(placePresent) .. ", PlaceName=" .. tostring(placeName), true)
            end

            local rawEvent = {
                id = world.event.S_EVENT_LAND,
                initiator = EventData.IniUnit or EventData.IniGroup or nil,
                place = EventData.Place or nil,
                -- Provide the original EventData for potential fallback use
                _moose_original = EventData
            }
            -- Log and return; the world event handler `cargoEventHandler` will handle the actual processing.
            return
        end
        -- Register the MOOSE handler
        TADC_CARGO_LANDING_HANDLER:HandleEvent(EVENTS.Land)
    end
    
    SCHEDULER:New(nil, detectThreats, {}, 5, TADC_SETTINGS.checkInterval)
    SCHEDULER:New(nil, monitorInterceptors, {}, 10, TADC_SETTINGS.monitorInterval)
    SCHEDULER:New(nil, checkAirbaseStatus, {}, 30, TADC_SETTINGS.statusReportInterval)
    SCHEDULER:New(nil, updateSquadronStates, {}, 60, 30) -- Update squadron states every 30 seconds (60 sec initial delay to allow DCS airbase coalition to stabilize)
    SCHEDULER:New(nil, cleanupOldDeliveries, {}, 60, 3600) -- Cleanup old delivery records every hour

    -- Start periodic squadron summary broadcast
    SCHEDULER:New(nil, broadcastSquadronSummary, {}, 10, TADC_SETTINGS.squadronSummaryInterval)
    
    log("Universal Dual-Coalition TADC operational!")
    log("RED Replenishment: " .. TADC_SETTINGS.red.cargoReplenishmentAmount .. " aircraft per cargo delivery")
    log("BLUE Replenishment: " .. TADC_SETTINGS.blue.cargoReplenishmentAmount .. " aircraft per cargo delivery")
    
    return true
end


initializeSystem()

-- Add F10 menu command for squadron summary
-- Use MenuManager to create coalition-specific menus (not mission-wide)
local menuRootBlue, menuRootRed

if MenuManager then
  menuRootBlue = MenuManager.CreateCoalitionMenu(coalition.side.BLUE, "TADC Utilities")
  menuRootRed = MenuManager.CreateCoalitionMenu(coalition.side.RED, "TADC Utilities")
else
  menuRootBlue = MENU_COALITION:New(coalition.side.BLUE, "TADC Utilities")
  menuRootRed = MENU_COALITION:New(coalition.side.RED, "TADC Utilities")
end

MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Squadron Resource Summary", menuRootRed, function()
    local summary = getSquadronResourceSummary(coalition.side.RED)
    MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
end)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Squadron Resource Summary", menuRootBlue, function()
    local summary = getSquadronResourceSummary(coalition.side.BLUE)
    MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
end)

-- 1. Show Airbase Status Report
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Airbase Status Report", menuRootRed, function()
    local report = "=== RED Airbase Status ===\n"
    for _, squadron in pairs(RED_SQUADRON_CONFIG) do
        local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.RED)
        local aircraftCount = squadronAircraftCounts.red[squadron.templateName] or 0
        local maxAircraft = squadron.aircraft
        local cooldown = squadronCooldowns.red[squadron.templateName]
        local cooldownStatus = ""
        if cooldown then
            local timeLeft = math.ceil((cooldown - timer.getTime()) / 60)
            if timeLeft > 0 then cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)" end
        end
        report = report .. string.format("%s: %s | Aircraft: %d/%d%s\n", squadron.displayName, status, aircraftCount, maxAircraft, cooldownStatus)
    end
    MESSAGE:New(report, 20):ToCoalition(coalition.side.RED)
end)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Airbase Status Report", menuRootBlue, function()
    local report = "=== BLUE Airbase Status ===\n"
    for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
        local usable, status = isAirbaseUsable(squadron.airbaseName, coalition.side.BLUE)
        local aircraftCount = squadronAircraftCounts.blue[squadron.templateName] or 0
        local maxAircraft = squadron.aircraft
        local cooldown = squadronCooldowns.blue[squadron.templateName]
        local cooldownStatus = ""
        if cooldown then
            local timeLeft = math.ceil((cooldown - timer.getTime()) / 60)
            if timeLeft > 0 then cooldownStatus = " (COOLDOWN: " .. timeLeft .. "m)" end
        end
        report = report .. string.format("%s: %s | Aircraft: %d/%d%s\n", squadron.displayName, status, aircraftCount, maxAircraft, cooldownStatus)
    end
    MESSAGE:New(report, 20):ToCoalition(coalition.side.BLUE)
end)

-- 2. Show Active Interceptors
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Active Interceptors", menuRootRed, function()
    local lines = {"Active RED Interceptors:"}
    for name, data in pairs(activeInterceptors.red) do
        if data and data.group and data.group:IsAlive() then
            table.insert(lines, string.format("%s (Squadron: %s, Threat: %s)", name, data.displayName or data.squadron, assignedThreats.red[name] or "N/A"))
        end
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Active Interceptors", menuRootBlue, function()
    local lines = {"Active BLUE Interceptors:"}
    for name, data in pairs(activeInterceptors.blue) do
        if data and data.group and data.group:IsAlive() then
            table.insert(lines, string.format("%s (Squadron: %s, Threat: %s)", name, data.displayName or data.squadron, assignedThreats.blue[name] or "N/A"))
        end
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE)
end)

-- 3. Show Threat Summary
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Threat Summary", menuRootRed, function()
    local lines = {"Detected BLUE Threats:"}
    if cachedSets.blueAircraft then
        cachedSets.blueAircraft:ForEach(function(group)
            if group and group:IsAlive() then
                table.insert(lines, string.format("%s (Size: %d)", group:GetName(), group:GetSize()))
            end
        end)
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Threat Summary", menuRootBlue, function()
    local lines = {"Detected RED Threats:"}
    if cachedSets.redAircraft then
        cachedSets.redAircraft:ForEach(function(group)
            if group and group:IsAlive() then
                table.insert(lines, string.format("%s (Size: %d)", group:GetName(), group:GetSize()))
            end
        end)
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE)
end)

-- 4. Request Immediate Squadron Summary Broadcast
MENU_COALITION_COMMAND:New(coalition.side.RED, "Broadcast Squadron Summary Now", menuRootRed, function()
    local summary = getSquadronResourceSummary(coalition.side.RED)
    MESSAGE:New(summary, 20):ToCoalition(coalition.side.RED)
end)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Broadcast Squadron Summary Now", menuRootBlue, function()
    local summary = getSquadronResourceSummary(coalition.side.BLUE)
    MESSAGE:New(summary, 20):ToCoalition(coalition.side.BLUE)
end)

-- 5. Show Cargo Delivery Log
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Cargo Delivery Log", menuRootRed, function()
    local lines = {"Recent RED Cargo Deliveries:"}
    if _G.processedDeliveries then
        for key, timestamp in pairs(_G.processedDeliveries) do
            if string.find(key, "RED") then
                table.insert(lines, string.format("%s at %d", key, timestamp))
            end
        end
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Cargo Delivery Log", menuRootBlue, function()
    local lines = {"Recent BLUE Cargo Deliveries:"}
    if _G.processedDeliveries then
        for key, timestamp in pairs(_G.processedDeliveries) do
            if string.find(key, "BLUE") then
                table.insert(lines, string.format("%s at %d", key, timestamp))
            end
        end
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE)
end)

-- 6. Show Zone Coverage Map
MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Zone Coverage Map", menuRootRed, function()
    local lines = {"RED Zone Coverage:"}
    for _, squadron in pairs(RED_SQUADRON_CONFIG) do
        local zones = {}
        if squadron.primaryZone then table.insert(zones, "Primary: " .. squadron.primaryZone) end
        if squadron.secondaryZone then table.insert(zones, "Secondary: " .. squadron.secondaryZone) end
        if squadron.tertiaryZone then table.insert(zones, "Tertiary: " .. squadron.tertiaryZone) end
        table.insert(lines, string.format("%s: %s", squadron.displayName, table.concat(zones, ", ")))
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.RED)
end)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Zone Coverage Map", menuRootBlue, function()
    local lines = {"BLUE Zone Coverage:"}
    for _, squadron in pairs(BLUE_SQUADRON_CONFIG) do
        local zones = {}
        if squadron.primaryZone then table.insert(zones, "Primary: " .. squadron.primaryZone) end
        if squadron.secondaryZone then table.insert(zones, "Secondary: " .. squadron.secondaryZone) end
        if squadron.tertiaryZone then table.insert(zones, "Tertiary: " .. squadron.tertiaryZone) end
        table.insert(lines, string.format("%s: %s", squadron.displayName, table.concat(zones, ", ")))
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToCoalition(coalition.side.BLUE)
end)

-- 7. Admin/Debug Commands - Create submenus under each coalition's TADC Utilities
local menuAdminBlue = MENU_COALITION:New(coalition.side.BLUE, "Admin / Debug", menuRootBlue)
local menuAdminRed = MENU_COALITION:New(coalition.side.RED, "Admin / Debug", menuRootRed)

MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Emergency Cleanup Interceptors", menuAdminBlue, function()
    local cleaned = 0
    for name, interceptors in pairs(activeInterceptors.red) do
        if interceptors and interceptors.group and not interceptors.group:IsAlive() then
            cleanupInterceptorEntry(name, "red")
            cleaned = cleaned + 1
        end
    end
    for name, interceptors in pairs(activeInterceptors.blue) do
        if interceptors and interceptors.group and not interceptors.group:IsAlive() then
            cleanupInterceptorEntry(name, "blue")
            cleaned = cleaned + 1
        end
    end
    MESSAGE:New("Cleaned up " .. cleaned .. " dead interceptor groups.", 20):ToBlue()
end)

MENU_COALITION_COMMAND:New(coalition.side.RED, "Emergency Cleanup Interceptors", menuAdminRed, function()
    local cleaned = 0
    for name, interceptors in pairs(activeInterceptors.red) do
        if interceptors and interceptors.group and not interceptors.group:IsAlive() then
            cleanupInterceptorEntry(name, "red")
            cleaned = cleaned + 1
        end
    end
    for name, interceptors in pairs(activeInterceptors.blue) do
        if interceptors and interceptors.group and not interceptors.group:IsAlive() then
            cleanupInterceptorEntry(name, "blue")
            cleaned = cleaned + 1
        end
    end
    MESSAGE:New("Cleaned up " .. cleaned .. " dead interceptor groups.", 20):ToRed()
end)

-- 9. Show System Uptime/Status
local systemStartTime = timer.getTime()
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show TADC System Status", menuAdminBlue, function()
    local uptime = math.floor((timer.getTime() - systemStartTime) / 60)
    local status = string.format("TADC System Uptime: %d minutes\nCheck Interval: %ds\nMonitor Interval: %ds\nStatus Report Interval: %ds\nSquadron Summary Interval: %ds\nCargo Check Interval: %ds", uptime, TADC_SETTINGS.checkInterval, TADC_SETTINGS.monitorInterval, TADC_SETTINGS.statusReportInterval, TADC_SETTINGS.squadronSummaryInterval, TADC_SETTINGS.cargoCheckInterval)
    MESSAGE:New(status, 20):ToBlue()
end)

MENU_COALITION_COMMAND:New(coalition.side.RED, "Show TADC System Status", menuAdminRed, function()
    local uptime = math.floor((timer.getTime() - systemStartTime) / 60)
    local status = string.format("TADC System Uptime: %d minutes\nCheck Interval: %ds\nMonitor Interval: %ds\nStatus Report Interval: %ds\nSquadron Summary Interval: %ds\nCargo Check Interval: %ds", uptime, TADC_SETTINGS.checkInterval, TADC_SETTINGS.monitorInterval, TADC_SETTINGS.statusReportInterval, TADC_SETTINGS.squadronSummaryInterval, TADC_SETTINGS.cargoCheckInterval)
    MESSAGE:New(status, 20):ToRed()
end)

-- 10. Check for Stuck Aircraft (manual trigger)
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Check for Stuck Aircraft", menuAdminBlue, function()
    monitorStuckAircraft()
    MESSAGE:New("Stuck aircraft check completed", 10):ToBlue()
end)

MENU_COALITION_COMMAND:New(coalition.side.RED, "Check for Stuck Aircraft", menuAdminRed, function()
    monitorStuckAircraft()
    MESSAGE:New("Stuck aircraft check completed", 10):ToRed()
end)

-- 11. Show Airbase Health Status
MENU_COALITION_COMMAND:New(coalition.side.BLUE, "Show Airbase Health Status", menuAdminBlue, function()
    local lines = {"Airbase Health Status:"}
    for _, coalitionKey in ipairs({"red", "blue"}) do
        local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
        table.insert(lines, coalitionName .. " Coalition:")
        for airbaseName, status in pairs(airbaseHealthStatus[coalitionKey]) do
            table.insert(lines, "  " .. airbaseName .. ": " .. status)
        end
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToBlue()
end)

MENU_COALITION_COMMAND:New(coalition.side.RED, "Show Airbase Health Status", menuAdminRed, function()
    local lines = {"Airbase Health Status:"}
    for _, coalitionKey in ipairs({"red", "blue"}) do
        local coalitionName = (coalitionKey == "red") and "RED" or "BLUE"
        table.insert(lines, coalitionName .. " Coalition:")
        for airbaseName, status in pairs(airbaseHealthStatus[coalitionKey]) do
            table.insert(lines, "  " .. airbaseName .. ": " .. status)
        end
    end
    MESSAGE:New(table.concat(lines, "\n"), 20):ToRed()
end)

-- Initialize airbase health status for all configured airbases
for _, coalitionKey in ipairs({"red", "blue"}) do
    local squadronConfig = getSquadronConfig(coalitionKey == "red" and coalition.side.RED or coalition.side.BLUE)
    for _, squadron in pairs(squadronConfig) do
        if not airbaseHealthStatus[coalitionKey][squadron.airbaseName] then
            airbaseHealthStatus[coalitionKey][squadron.airbaseName] = "operational"
        end
    end
end

-- Set up periodic stuck aircraft monitoring (every 2 minutes)
SCHEDULER:New(nil, monitorStuckAircraft, {}, 120, 120)



