// Golf System Main Entry Point
// Orchestrates all golf system components and handles event registration

import { world, system, EntityComponentTypes, EquipmentSlot } from "@minecraft/server";
import { ModalFormData } from "@minecraft/server-ui";
import { 
    GOLFBALL_ID,
    LEDGER_ID,
    CLUB_CONFIGS,
    CLUB_SOUNDS,
    PHYSICS,
    TRAIL_SETTINGS,
    PARTICLES,
    SOUNDS,
    INTERVALS,
    updateClubStrengthByRenderDistance,
    getCurrentRenderDistance,
    GOLFBALL_SPAWN_EGG_ID
} from './config.js';
import { 
    getLookDirection,
    calculateDistance,
    isWithinWorldBounds,
    clampVelocity,
    playSoundWithFallback,
    createRandomOffset,
    isPlayerLookingAtEntity,
    findClosestEntity,
    validateFormValues
} from './utils.js';
import {
    incrementSwingCount,
    resetSwingCount,
    getSwingCount,
    updateLedger,
    handleLedgerClick,
    handleDrivingRangeReturn,
    getPlayerGamemode,
    cancelBallReturnTimer,
    getPlayerParticlePreference,
    djjawhda
} from './score.js';
import './bag.js'; // Initialize golf ball system
import { processBallLanding, processEnhancedBallTracking, markBallAsHit, cleanupBallHitStates } from './blocks.js'; // Golf ball landing detection system
import { golfCameraSystem } from './camera.js'; // Golf ball camera system
import './flag.js';
import './ball.js';
import { validateHoleDetection, validateBallSpeed, cleanupValidationHistory } from './holeValidation.js';
import { clearBallTracking, initializeBallTracking, validateAndSaveBallLocation } from './precise_tracking.js';
import {
    getPlayerClubOptions,
    setPlayerClubOptions,
    clearPlayerClubOptions,
    getPlayerActiveBallId,
    setPlayerActiveBallId
} from './persistence.js';

// Global state management (session-only data)
const lastHitGolfballs = new Map(); // Track last hit golf ball per player (session-only)
const golfballTrails = new Map(); // Track golf ball trails (session-only)
const trailDisabled = new Map(); // Track which balls have trail disabled due to nearby players (session-only)

/**
 * Get club options for a specific player and club type
 * @param {string} playerId - Player ID
 * @param {string} clubType - Club type ID
 * @returns {Object|null} Club options or null if not set
 */
function getClubOptions(playerId, clubType) {
    return getPlayerClubOptions(playerId, clubType);
}

/**
 * Set club options for a specific player and club type
 * @param {string} playerId - Player ID
 * @param {string} clubType - Club type ID
 * @param {Object} options - Club options {strength, arcAngle}
 */
function setClubOptions(playerId, clubType, options) {
    setPlayerClubOptions(playerId, clubType, options);
}

/**
 * Clear all club options for a player
 * @param {string} playerId - Player ID
 */
function clearAllClubOptions(playerId) {
    clearPlayerClubOptions(playerId);
}

/**
 * Show club configuration form for a specific club type
 * @param {Player} player - The player
 * @param {string} clubType - Club type ID
 */
function showClubConfigForm(player, clubType) {
    const config = CLUB_CONFIGS[clubType];
    if (!config) return;

    const form = new ModalFormData()
        .title(`${config.name.charAt(0).toUpperCase() + config.name.slice(1)} Options`)

        if (config.name !== "driver") {
            form.slider("Strength", config.strengthRange[0], config.strengthRange[1]);
        }

    // Add arc angle slider if the club supports it
    if (config.arcRange[0] !== config.arcRange[1]) {
        form.slider(`Arc Angle (${config.arcRange[0]}-${config.arcRange[1]}°)`, config.arcRange[0], config.arcRange[1]);
    }

    form.show(player).then(response => {
        if (response.canceled || !Array.isArray(response.formValues)) return;

        const validatedValues = validateFormValues(response.formValues);

        // console.log(validatedValues);
        let strength;
        let arcAngle;
        let multstrength;
        if (config.name !== "driver") {
            [multstrength, arcAngle] = validatedValues;
        } else {
            [arcAngle] = validatedValues;
            multstrength = 1;
        }

        // Apply club-specific calculations
        arcAngle = arcAngle || config.arcRange[0]; // Use minimum arc if not provided
        strength = (config.name === "driver" && arcAngle > 10) ? 14 : multstrength * config.strengthMultiplier;
        // console.log(config.name, arcAngle, strength);
        setClubOptions(player.id, clubType, { strength, arcAngle });

        const arcMessage = arcAngle === 0 ? "(flat shot)" : `arc ${arcAngle}°`;
        player.onScreenDisplay.setActionBar(`${config.name} options set: ${strength} strength, ${arcMessage}`);
    });
}

/**
 * Start trail effect for a golf ball
 * @param {Entity} golfball - The golf ball entity
 */
function startTrailEffect(golfball) {
    const ballId = golfball.id;
    
    golfballTrails.set(ballId, {
        startTime: Date.now(),
        stationaryTicks: 0,
        lastPosition: {
            x: golfball.location.x,
            y: golfball.location.y,
            z: golfball.location.z
        }
    });
}

/**
 * Create trail effect between previous and current ball positions
 * @param {Entity} golfball - The golf ball entity
 * @param {Object} lastPosition - Previous position {x, y, z}
 */
function createTrailEffect(golfball, lastPosition) {
    const currentPos = golfball.location;
    const dimension = golfball.dimension;
    const ballId = golfball.id;
    
    // Get the player who owns this ball to use their particle preference
    const owner = golfball.getDynamicProperty("owner");
    let selectedParticle = PARTICLES.PRIMARY; // Default particle
    
    if (owner) {
        // Find the player by name
        const player = world.getAllPlayers().find(p => p.name === owner);
        if (player) {
            selectedParticle = getPlayerParticlePreference(player.id);
        }
    }
    
    // Check if trail is disabled for this ball
    if (trailDisabled.get(ballId)) {
        return;
    }
    
    // If no previous position, spawn particles at current location
    if (!lastPosition) {
        for (let i = 0; i < TRAIL_SETTINGS.PARTICLES_PER_TRAIL; i++) {
            const offset = createRandomOffset();
            try {
                dimension.spawnParticle(selectedParticle, {
                    x: currentPos.x,
                    y: currentPos.y + 0.2,
                    z: currentPos.z
                });
            } catch (error) {
                // Fallback to basic particle if custom fails
                dimension.spawnParticle(PARTICLES.FALLBACK_1, {
                    x: currentPos.x,
                    y: currentPos.y + 0.2,
                    z: currentPos.z
                });
            }
        }
        return;
    }
    
    // Calculate distance between previous and current position
    const distance = calculateDistance(currentPos, lastPosition);
    
    // If ball hasn't moved much, don't create line
    if (distance < TRAIL_SETTINGS.MINIMUM_MOVEMENT_THRESHOLD) {
        return;
    }
    
    // Create line of particles between previous and current position
    const steps = Math.max(2, Math.floor(distance * TRAIL_SETTINGS.LINE_PARTICLES_MULTIPLIER));
    
    for (let i = 0; i <= steps; i++) {
        const t = i / steps;
        const linePos = {
            x: lastPosition.x + ((currentPos.x - lastPosition.x) * t),
            y: lastPosition.y + ((currentPos.y - lastPosition.y) * t),
            z: lastPosition.z + ((currentPos.z - lastPosition.z) * t)
        };
        
        const offset = createRandomOffset(0.1);
        try {
            dimension.spawnParticle(selectedParticle, {
                x: linePos.x,
                y: linePos.y + 0.2,
                z: linePos.z
            });
        } catch (error) {
            // Fallback to basic particle if custom fails
            dimension.spawnParticle(PARTICLES.FALLBACK_1, {
                x: linePos.x,
                y: linePos.y + 0.2,
                z: linePos.z
            });
        }
    }
}

/**
 * Handle golf ball hit by player with club
 * @param {Entity} hitEntity - The golf ball entity
 * @param {Player} player - The player hitting the ball
 * @param {string} clubType - The club type being used
 */
function handleGolfBallHit(hitEntity, player, clubType) {
    // Check ownership
    const ballOwner = hitEntity.getDynamicProperty("owner");
    if (ballOwner && ballOwner !== player.name) {
        player.onScreenDisplay.setActionBar(`§cThis is ${ballOwner}'s golf ball - you cannot hit it!`);
        return;
    }
    
    // Assign ownership if not owned
    if (!ballOwner) {
        // Check if player already owns another ball
        const existingBalls = player.dimension.getEntities({
            type: GOLFBALL_ID
        }).filter(ball => {
            const owner = ball.getDynamicProperty("owner");
            return owner === player.name && ball.id !== hitEntity.id;
        });
        
        if (existingBalls.length > 0) {
            player.onScreenDisplay.setActionBar(`§cYou already own another golf ball! You can only own one at a time.`);
            player.runCommand('give @s dss_nocturne:dss_normal_golfball_placer');
            return;
        }
        
        hitEntity.setDynamicProperty("owner", player.name);
        hitEntity.nameTag = `§b${player.name}'s Ball`;
        setPlayerActiveBallId(player.id, hitEntity.id);
        player.onScreenDisplay.setActionBar(`§aYou now permanently own this golf ball!`);
    }

    // Get club options
    const options = getClubOptions(player.id, clubType);
    let clubName;
    if (!options) {
        clubName = CLUB_CONFIGS[clubType]?.name || "club";
        player.onScreenDisplay.setActionBar(`Please configure your ${clubName} options first by right-clicking with the club.`);
        return;
    } else {
        clubName = CLUB_CONFIGS[clubType].name;
    }

    // Increment swing counter and track hit
    const swings = incrementSwingCount(player.id);
    lastHitGolfballs.set(player.id, hitEntity);
    hitEntity.setDynamicProperty("lastHitBy", player.id);

    // Play sounds and effects
    const ballLocation = hitEntity.location;
    const dimension = hitEntity.dimension;

    // console.log(clubName);

    if (clubName === "putter") {
        playSoundWithFallback(dimension, SOUNDS.PUTTING, SOUNDS.FALLBACK_SWING, ballLocation);
    } else if (clubName === "driver") {
        playSoundWithFallback(dimension, SOUNDS.DRIVER, SOUNDS.FALLBACK_SWING, ballLocation);
    } else if (clubName === "fairway") {
        playSoundWithFallback(dimension, SOUNDS.FAIRWAY, SOUNDS.FALLBACK_SWING, ballLocation);
    } else if (clubName === "wedge" && world.getDimension(hitEntity.dimension.id).getBlockBelow(hitEntity.location).typeId.includes("bunker")) { 
        hitEntity.addTag("dss_wedged");
    } else { }
    
    playSoundWithFallback(dimension, CLUB_SOUNDS[clubType], SOUNDS.FALLBACK_SWING, ballLocation);
    playSoundWithFallback(dimension, SOUNDS.BALL_HIT, SOUNDS.FALLBACK_HIT, ballLocation, { volume: 0.6 });
    
    // Spawn hit particle
    try {
        dimension.spawnParticle(PARTICLES.HIT_EFFECT, {
            x: ballLocation.x,
            y: ballLocation.y + 0.5,
            z: ballLocation.z
        });
    } catch (error) {
        // Silently fail if particle spawn fails
    }

    // Get render distance for power adjustment
    const renderDistance = getCurrentRenderDistance(player);
    
    // Update club strength based on render distance
    const updatedConfig = updateClubStrengthByRenderDistance(CLUB_CONFIGS[clubType], renderDistance);
    
    // Calculate and apply velocity
    const { strength, arcAngle } = options;
    const dirVector = getLookDirection(player);
    const arcRadians = arcAngle * (Math.PI / 180);
    
    // Apply render distance adjusted strength multiplier
    const adjustedStrength = strength * updatedConfig.strengthMultiplier;
    const yComponent = Math.sin(arcRadians) * adjustedStrength;
    const horizontalStrength = Math.cos(arcRadians) * adjustedStrength;

    // Clamp velocities and check world boundaries
    const clampedVelocity = clampVelocity(yComponent);
    const clampedHorizontal = clampVelocity(horizontalStrength);

    const currentLocation = hitEntity.location;
    const targetLocation = {
        x: currentLocation.x + (dirVector.x * clampedHorizontal * 0.5),
        y: currentLocation.y + (clampedVelocity * 0.5),
        z: currentLocation.z + (dirVector.z * clampedHorizontal * 0.5)
    };

    if (isWithinWorldBounds(targetLocation)) {
        hitEntity.applyImpulse({
            x: dirVector.x * clampedHorizontal,
            y: clampedVelocity,
            z: dirVector.z * clampedHorizontal
        });
    } else {
        // Apply reduced velocity to keep ball in play
        hitEntity.applyImpulse({
            x: dirVector.x * PHYSICS.SAFE_VELOCITY_FALLBACK,
            y: PHYSICS.SAFE_Y_VELOCITY_FALLBACK,
            z: dirVector.z * PHYSICS.SAFE_VELOCITY_FALLBACK
        });
        player.sendMessage("§cShot adjusted to stay within world boundaries");
    }
    
    // Start trail effect
    startTrailEffect(hitEntity);
    
    // Mark ball as hit for landing detection
    markBallAsHit(hitEntity);

    // Start camera tracking for the golf ball
    golfCameraSystem.startBallTracking(player, hitEntity);

    clubName = CLUB_CONFIGS[clubType]?.name || "club";
    const arcMessage = arcAngle === 0 ? "(flat shot)" : `and arc ${arcAngle}°`;
    // const finalRenderDistance = getCurrentRenderDistance(player);
    // const finalUpdatedConfig = updateClubStrengthByRenderDistance(CLUB_CONFIGS[clubType], finalRenderDistance);
    // player.sendMessage(`You hit the golfball with ${clubName}: strength ${strength} ${arcMessage}`);

    system.runTimeout(() => {
        if (hitEntity.isValid) hitEntity.removeTag("dss_wedged");
    }, 10);
}

// Event Listeners

// Handle entity spawning (golf ball ownership assignment)
world.afterEvents.entitySpawn.subscribe((event) => {
    const { entity, cause } = event;
    
    if (entity.typeId === GOLFBALL_ID && cause === "Spawned") {
        const nearbyPlayers = entity.dimension.getEntities({
            type: "minecraft:player",
            location: entity.location,
            maxDistance: 5
        });
        
        if (nearbyPlayers.length > 0) {
            const closestPlayer = findClosestEntity(nearbyPlayers, entity.location);
            
            // Check gamemode for driving range vs golf course
            const gamemode = getPlayerGamemode(closestPlayer.id);
            
            if (gamemode === 'driving_range') {
                // Driving range mode - enforce one-ball limit (same as golf course)
                const existingBalls = entity.dimension.getEntities({
                    type: GOLFBALL_ID
                }).filter(ball => {
                    const owner = ball.getDynamicProperty("owner");
                    return owner === closestPlayer.name && ball.id !== entity.id;
                });
                
                if (existingBalls.length > 0) {
                    closestPlayer.onScreenDisplay.setActionBar("§cYou already have a golf ball! You can only spawn one at a time.");
                    closestPlayer.runCommand('give @s dss_nocturne:dss_normal_golfball_placer');
                    entity.remove();
                    return;
                }
                
                // Track this new ball
                entity.setDynamicProperty("owner", closestPlayer.name);
                entity.setDynamicProperty("ownerId", closestPlayer.id);
                entity.setDynamicProperty("spawnTime", Date.now());
                entity.nameTag = `§b${closestPlayer.name}'s Ball`;
                setPlayerActiveBallId(closestPlayer.id, entity.id);
                
                // Initialize precise location tracking for spawned ball
                initializeBallTracking(entity, closestPlayer);
                
                closestPlayer.onScreenDisplay.setActionBar("§aGolf ball spawned in driving range mode!");
                
            } else {
                // Golf course mode - enforce one-ball limit
                const existingBalls = entity.dimension.getEntities({
                    type: GOLFBALL_ID
                }).filter(ball => {
                    const owner = ball.getDynamicProperty("owner");
                    return owner === closestPlayer.name && ball.id !== entity.id;
                });
                
                if (existingBalls.length > 0) {
                    closestPlayer.onScreenDisplay.setActionBar("§cYou already have a golf ball! You can only spawn one at a time.");
                    closestPlayer.runCommand('give @s dss_nocturne:dss_normal_golfball_placer');
                    entity.remove();
                    return;
                }
                
                // Auto-assign ownership
                entity.setDynamicProperty("owner", closestPlayer.name);
                entity.setDynamicProperty("ownerId", closestPlayer.id);
                entity.nameTag = `§b${closestPlayer.name}'s Ball`;
                setPlayerActiveBallId(closestPlayer.id, entity.id);
                
                // Initialize precise location tracking for spawned ball
                initializeBallTracking(entity, closestPlayer);
                
                closestPlayer.onScreenDisplay.setActionBar("§aGolf ball spawned! You now own this ball.");
            }
        }
    }
});

// Handle item use (club configuration)
world.afterEvents.itemUse.subscribe((event) => {
    const { source, itemStack } = event;
    if (!source || source.typeId !== "minecraft:player" || !itemStack) return;

    const clubType = itemStack.typeId;
    if (CLUB_CONFIGS[clubType]) {
        showClubConfigForm(source, clubType);
    } else if (itemStack.typeId === LEDGER_ID) {
        handleLedgerClick(source);
    } else {
        // Handle golf ball claiming with other items
        const lookDirection = getLookDirection(source);
        const golfballs = source.dimension.getEntities({ 
            type: GOLFBALL_ID,
            location: source.location,
            maxDistance: 10 
        });
        
        for (const golfball of golfballs) {
            if (isPlayerLookingAtEntity(source, golfball)) {
                const currentOwner = golfball.getDynamicProperty("owner");
                if (currentOwner && currentOwner !== source.name) {
                    source.sendMessage(`§cThis golf ball is already owned by ${currentOwner}!`);
                    return;
                }
                
                if (currentOwner === source.name) {
                    source.sendMessage(`§eYou already own this golf ball!`);
                    return;
                }
                
                // Check if player already owns another ball
                const existingBalls = source.dimension.getEntities({
                    type: GOLFBALL_ID
                }).filter(ball => {
                    const owner = ball.getDynamicProperty("owner");
                    return owner === source.name && ball.id !== golfball.id;
                });
                
                if (existingBalls.length > 0) {
                    source.sendMessage(`§cYou already own another golf ball! You can only own one at a time.`);
                    source.runCommand('give @s dss_nocturne:dss_normal_golfball_placer');
                    return;
                }
                
                // Assign ownership
                golfball.setDynamicProperty("owner", source.name);
                golfball.setDynamicProperty("ownerId", source.id);
                golfball.setDynamicProperty("lastHitBy", source.id);
                golfball.nameTag = `§b${source.name}'s Ball`;
                setPlayerActiveBallId(source.id, golfball.id);
                
                source.sendMessage(`§aGolf ball claimed! You now own this ball.`);
                return;
            }
        }
    }
});

// Handle entity attacks (swing sounds for non-golf ball hits)
world.afterEvents.entityHurt.subscribe((event) => {
    const { hurtEntity, damageSource } = event;
    if (!damageSource?.damagingEntity) return;
    
    const attacker = damageSource.damagingEntity;
    if (attacker.typeId !== "minecraft:player") return;
    
    const mainhand = attacker.getComponent(EntityComponentTypes.Equippable)?.getEquipment(EquipmentSlot.Mainhand);
    if (!mainhand) return;
    
    const clubType = mainhand.typeId;
    if (!CLUB_CONFIGS[clubType]) return;
    
    // Only play swing sound if not hitting a golf ball
    if (hurtEntity.typeId !== GOLFBALL_ID) {
        playSoundWithFallback(
            attacker.dimension, 
            CLUB_SOUNDS[clubType], 
            SOUNDS.FALLBACK_SWING, 
            attacker.location, 
            { volume: 0.3 }
        );
    }
});

// Handle entity hits (golf ball physics)
world.afterEvents.entityHitEntity.subscribe((event) => {
    const { hitEntity, damagingEntity } = event;
    if (!hitEntity || !damagingEntity) return;
    if (hitEntity.typeId !== GOLFBALL_ID || damagingEntity.typeId !== "minecraft:player") return;

    const mainhand = damagingEntity.getComponent(EntityComponentTypes.Equippable)?.getEquipment(EquipmentSlot.Mainhand);
    if (!mainhand) return;

    const clubType = mainhand.typeId;
    if (CLUB_CONFIGS[clubType]) {
        handleGolfBallHit(hitEntity, damagingEntity, clubType);
    }
});

// Main game loop
system.runInterval(() => {
    const golfballs = world.getDimension("overworld").getEntities({ type: GOLFBALL_ID });
    
    for (const golfball of golfballs) {
        const location = golfball.location;
        const dimension = golfball.dimension;
        
        // Handle trail effects
        const ballId = golfball.id;
        const trailData = golfballTrails.get(ballId);
        if (trailData) {
            try {
                // Check for nearby players to disable trail
                const allPlayers = world.getAllPlayers();
                let playerNearby = false;
                for (const player of allPlayers) {
                    if (player.dimension.id === golfball.dimension.id) {
                        const distance = calculateDistance(player.location, golfball.location);
                        if (distance <= TRAIL_SETTINGS.NEARBY_PLAYER_DISABLE_DISTANCE) {
                            playerNearby = true;
                            break;
                        }
                    }
                }
                
                trailDisabled.set(ballId, playerNearby);
                
                // Create trail effect
                createTrailEffect(golfball, trailData.lastPosition);
                
                // Update last position
                trailData.lastPosition = {
                    x: location.x,
                    y: location.y,
                    z: location.z
                };
                
                // Check for cleanup
                const velocity = golfball.getVelocity();
                const speed = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
                
                if (speed < 0.01) {
                    trailData.stationaryTicks++;
                    if (trailData.stationaryTicks > TRAIL_SETTINGS.STATIONARY_CLEANUP_TICKS) {
                        golfballTrails.delete(ballId);
                        trailDisabled.delete(ballId);
                    }
                } else {
                    trailData.stationaryTicks = 0;
                }
            } catch (error) {
                golfballTrails.delete(ballId);
                trailDisabled.delete(ballId);
            }
        }
        
        // Ensure name tag is visible for owned balls
        const owner = golfball.getDynamicProperty("owner");
        if (owner) {
            golfball.nameTag = `§b${owner}'s Ball`;
        }
        
        // Check gamemode and handle accordingly
        const playerId = golfball.getDynamicProperty("lastHitBy");
        const player = world.getAllPlayers().find(p => p.id === playerId);
        const gamemode = player ? getPlayerGamemode(player.id) : 'golf_course';
        
        // Process ball landing detection for special blocks (only if not in hole detection)
        let ballRemovedByHole = false;
        
        if (gamemode === 'driving_range') {
            // Driving range mode - schedule ball return instead of hole detection
            if (player && golfball.getDynamicProperty("owner") === player.name) {
                // Check if ball has stopped moving
                const velocity = golfball.getVelocity();
                const speed = Math.sqrt(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z);
                
                if (speed < 0.1) { // Ball has stopped or is barely moving
                    handleDrivingRangeReturn(golfball, player);
                }
            }
        } else {
            // Golf course mode - enhanced hole detection with validation
            const holeDetection = validateHoleDetection(golfball, dimension);
            
            if (holeDetection) {
                const ballSpeed = holeDetection.ballSpeed;
                
                // Use dynamic speed validation based on detection method
                if (validateBallSpeed(ballSpeed, holeDetection.method)) {
                    if (player) {
                        const swings = getSwingCount(player.id);
                        const holeLocation = holeDetection.holeLocation;
                        
                        // Clear player's active ball tracking
                        const ballOwner = golfball.getDynamicProperty("owner");
                        const ballOwnerId = golfball.getDynamicProperty("ownerId");
                        if (ballOwner && ballOwnerId) {
                            setPlayerActiveBallId(ballOwnerId, null);
                            cancelBallReturnTimer(golfball.id); // Cancel any pending ball return
                        }
                        
                        // Clear precise tracking data before removing ball
                        clearBallTracking(golfball.id);
                        ballRemovedByHole = true;
                        
                        // Remove the golf ball
                        golfball.remove();
                        player.runCommand(`give @s ${GOLFBALL_SPAWN_EGG_ID}`);
                        
                        // Update scoring with hole coordinates
                        updateLedger(player, swings, holeLocation);
                        
                        const flavor = djjawhda(swings);
                        player.onScreenDisplay.setActionBar(`§6HOLE! §r${flavor}`);
                        player.runCommand(`playsound ${SOUNDS.HOLE} @a[r=20] ${holeLocation.x} ${holeLocation.y} ${holeLocation.z}`)
                        
                        // Reset for next hole
                        resetSwingCount(player.id);
                        clearAllClubOptions(player.id);
                        player.sendMessage(`§7Stroke count reset for next hole!`);
                    }
                }
            }
            
            // Only process landing detection if ball wasn't removed by hole scoring
            if (!ballRemovedByHole) {
                // Use enhanced ball tracking instead of just processBallLanding
                // This integrates continuous monitoring, validation, and landing processing
                processEnhancedBallTracking(golfball);
            }
        }
        
        
        // Cleanup old ball hit states periodically
        if (Math.random() < 0.01) { // ~1% chance per tick to avoid performance impact
            cleanupBallHitStates();
            cleanupValidationHistory(); // Also cleanup validation history
        }
    }
}, INTERVALS.MAIN_LOOP_TICKS);

// Ball Location Validator - runs every 1 second to catch and save valid locations
// This provides redundancy for the landing detection system and catches balls that settle naturally
system.runInterval(() => {
    const golfballs = world.getDimension("overworld").getEntities({ type: GOLFBALL_ID });
    
    for (const golfball of golfballs) {
        try {
            // Only validate balls that have owners
            const owner = golfball.getDynamicProperty("owner");
            if (owner) {
                validateAndSaveBallLocation(golfball);
            }
        } catch (error) {
            // Silently continue if there's an issue with a specific golf ball
            continue;
        }
    }
}, 20); // 20 ticks = 1 second