// modules/dss_sound_tagging.js
import { world, system, Player } from '@minecraft/server';
import { CONFIG } from '../dss_sound_config.js';
import { log, playSound, canPlaySound, stopSound } from './dss_sound_helper.js';

/**
 * REDESIGNED TAG-BASED SOUND SYSTEM
 * 
 * This system now:
 * - Processes ALL player tags every interval
 * - Maintains comprehensive state tracking
 * - Immediately stops sounds when tags are removed
 * - Handles both looping and non-looping sounds properly
 * - Provides better debugging and error handling
 */

// Core data structures for the new system
const playerStates = new Map(); // playerName -> PlayerSoundState

/**
 * Player Sound State class to track all sound-related data for a player
 */
class PlayerSoundState {
  constructor(playerName) {
    this.playerName = playerName;
    this.currentTags = new Set();           // Current tags the player has
    this.activeSounds = new Map();          // tag -> SoundInstance
    this.soundCooldowns = new Map();        // soundName -> lastPlayTime
    this.playCounters = new Map();          // tag -> remaining plays
    this.lastTagUpdate = Date.now();
    
    log(`Initialized sound state for player "${playerName}"`, 'TAGGING');
  }

  /**
   * Update the player's current tags and detect changes
   * @param {string[]} newTags 
   * @returns {Object} {added: Set, removed: Set, current: Set}
   */
  updateTags(newTags) {
    const newTagSet = new Set(newTags);
    const added = new Set([...newTagSet].filter(tag => !this.currentTags.has(tag)));
    const removed = new Set([...this.currentTags].filter(tag => !newTagSet.has(tag)));
    
    this.currentTags = newTagSet;
    this.lastTagUpdate = Date.now();
    
    if (added.size > 0 || removed.size > 0) {
      log(`Player "${this.playerName}" tag changes - Added: [${[...added].join(', ')}], Removed: [${[...removed].join(', ')}]`, 'TAGGING');
    }
    
    return { added, removed, current: newTagSet };
  }

  /**
   * Start a sound for a specific tag
   * @param {string} tag 
   * @param {Object} config 
   * @param {Player} player 
   */
  startSound(tag, config, player) {
    // Check if already playing
    if (this.activeSounds.has(tag)) {
      log(`Sound for tag "${tag}" already active for player "${this.playerName}"`, 'TAGGING');
      return;
    }

    // Check cooldown using the new method
    const cooldownMs = (config.COOLDOWN || 1) * 1000;
    const { onCooldown, remainingMs } = this.checkCooldown(config.SOUNDNAME, cooldownMs);
    if (onCooldown) {
      log(`Sound "${config.SOUNDNAME}" on cooldown for player "${this.playerName}" (${Math.ceil(remainingMs/1000)}s remaining)`, 'TAGGING');
      return;
    }

    // Initialize play counter
    if (config.PLAYTIMES > 0) {
      this.playCounters.set(tag, config.PLAYTIMES);
    }

    const isLooping = config.PLAYTIMES === 0 || config.PLAYTIMES === "loop";
    
    try {
      // Stop any existing instance to prevent stacking
      this.stopSound(tag, player, 'restart');
      
      // Play the sound
      playSound(player, config.SOUNDNAME, {
        volume: config.VOLUME || 1.0,
        pitch: config.PITCH || 1.0,
        loop: isLooping
      });

      // Track this sound
      const soundInstance = {
        soundName: config.SOUNDNAME,
        isLooping: isLooping,
        startTime: Date.now(),
        config: config
      };
      
      this.activeSounds.set(tag, soundInstance);
      this.soundCooldowns.set(config.SOUNDNAME, Date.now());
      
      log(`Started sound "${config.SOUNDNAME}" for tag "${tag}" on player "${this.playerName}" (looping: ${isLooping})`, 'TAGGING');
      
      // For non-looping sounds with limited plays, schedule next play or cleanup
      if (!isLooping && config.PLAYTIMES > 1) {
        this.scheduleNextPlay(tag, config, player, cooldownMs);
      } else if (!isLooping && config.PLAYTIMES === 1) {
        this.scheduleCleanup(tag, player, 3000); // Clean up after 3 seconds for single-play sounds
      }
      
    } catch (error) {
      log(`Error starting sound for tag "${tag}": ${error.message}`, 'TAGGING');
    }
  }

  /**
   * Stop a sound for a specific tag
   * @param {string} tag 
   * @param {Player} player 
   * @param {string} reason 
   */
  stopSound(tag, player, reason = 'tag removed') {
    const soundInstance = this.activeSounds.get(tag);
    if (soundInstance) {
      try {
        stopSound(player, soundInstance.soundName);
        
        // Reset cooldown when sound is stopped so it can be played immediately if tag is re-added
        this.soundCooldowns.delete(soundInstance.soundName);
        
        this.activeSounds.delete(tag);
        this.playCounters.delete(tag);
        
        log(`Stopped sound "${soundInstance.soundName}" for tag "${tag}" on player "${this.playerName}" (reason: ${reason}) - Cooldown reset`, 'TAGGING');
      } catch (error) {
        log(`Error stopping sound for tag "${tag}": ${error.message}`, 'TAGGING');
      }
    }
  }

  /**
   * Stop all sounds for this player
   * @param {Player} player 
   */
  stopAllSounds(player) {
    for (const [tag, soundInstance] of this.activeSounds.entries()) {
      try {
        stopSound(player, soundInstance.soundName);
        log(`Stopped sound "${soundInstance.soundName}" for tag "${tag}" on player leave`, 'TAGGING');
      } catch (error) {
        log(`Error stopping sound on player leave: ${error.message}`, 'TAGGING');
      }
    }
    
    // Clear all tracking data including cooldowns
    this.activeSounds.clear();
    this.playCounters.clear();
    this.soundCooldowns.clear(); // Reset all cooldowns when player leaves
    
    log(`Cleared all sound data and cooldowns for player "${this.playerName}"`, 'TAGGING');
  }

  /**
   * Schedule next play for multi-play sounds
   * @param {string} tag 
   * @param {Object} config 
   * @param {Player} player 
   * @param {number} cooldownMs 
   */
  scheduleNextPlay(tag, config, player, cooldownMs) {
    system.runTimeout(() => {
      // Check if player still has the tag and the sound is still active
      if (this.currentTags.has(tag) && this.activeSounds.has(tag)) {
        const remaining = this.playCounters.get(tag) || 0;
        if (remaining > 1) {
          this.playCounters.set(tag, remaining - 1);
          this.stopSound(tag, player, 'next play');
          this.startSound(tag, config, player);
        } else {
          // Finished all plays, remove tag and stop sound
          this.stopSound(tag, player, 'finished plays');
          try {
            player.removeTag(tag);
            log(`Removed tag "${tag}" from player "${this.playerName}" after completing all plays`, 'TAGGING');
          } catch (error) {
            log(`Error removing tag "${tag}": ${error.message}`, 'TAGGING');
          }
        }
      }
    }, Math.max(cooldownMs / 50, 20)); // Convert to ticks, minimum 1 second
  }

  /**
   * Schedule cleanup for single-play sounds
   * @param {string} tag 
   * @param {Player} player 
   * @param {number} delayMs 
   */
  scheduleCleanup(tag, player, delayMs) {
    system.runTimeout(() => {
      this.stopSound(tag, player, 'single play finished');
    }, delayMs / 50); // Convert to ticks
  }

  /**
   * Get debug information about this player's sound state
   */
  getDebugInfo() {
    return {
      playerName: this.playerName,
      currentTags: [...this.currentTags],
      activeSounds: Object.fromEntries(
        [...this.activeSounds.entries()].map(([tag, sound]) => [
          tag, 
          { soundName: sound.soundName, isLooping: sound.isLooping, age: Date.now() - sound.startTime }
        ])
      ),
      playCounters: Object.fromEntries(this.playCounters),
      soundCooldowns: Object.fromEntries(
        [...this.soundCooldowns.entries()].map(([sound, time]) => [
          sound, 
          { lastPlayed: time, cooldownRemaining: Math.max(0, time + 3000 - Date.now()) }
        ])
      )
    };
  }

  /**
   * Reset cooldown for a specific sound
   * @param {string} soundName 
   */
  resetCooldown(soundName) {
    if (this.soundCooldowns.has(soundName)) {
      this.soundCooldowns.delete(soundName);
      log(`Reset cooldown for sound "${soundName}" for player "${this.playerName}"`, 'TAGGING');
      return true;
    }
    return false;
  }

  /**
   * Reset all cooldowns for this player
   */
  resetAllCooldowns() {
    const count = this.soundCooldowns.size;
    this.soundCooldowns.clear();
    log(`Reset all ${count} cooldowns for player "${this.playerName}"`, 'TAGGING');
  }

  /**
   * Check if a sound is on cooldown
   * @param {string} soundName 
   * @param {number} cooldownMs 
   * @returns {Object} {onCooldown: boolean, remainingMs: number}
   */
  checkCooldown(soundName, cooldownMs) {
    const lastPlayTime = this.soundCooldowns.get(soundName) || 0;
    const remainingMs = Math.max(0, (lastPlayTime + cooldownMs) - Date.now());
    return {
      onCooldown: remainingMs > 0,
      remainingMs: remainingMs
    };
  }
}

/**
 * Get or create player state
 * @param {string} playerName 
 * @returns {PlayerSoundState}
 */
function getPlayerState(playerName) {
  if (!playerStates.has(playerName)) {
    playerStates.set(playerName, new PlayerSoundState(playerName));
  }
  return playerStates.get(playerName);
}

/**
 * Clean up player state
 * @param {string} playerName 
 */
function cleanupPlayerState(playerName) {
  playerStates.delete(playerName);
  log(`Cleaned up sound state for player "${playerName}"`, 'TAGGING');
}

/**
 * Get enabled tag configurations
 * @returns {Map} tag -> config
 */
function getEnabledTagConfigs() {
  const configs = new Map();
  if (CONFIG.SOUNDS && CONFIG.SOUNDS.TAGS) {
    for (const [tagName, config] of Object.entries(CONFIG.SOUNDS.TAGS)) {
      if (config && config.PLAY) {
        configs.set(tagName, config);
      }
    }
  }
  return configs;
}

/**
 * Process a player's tags and manage sounds accordingly
 * @param {Player} player 
 */
function processPlayerTags(player) {
  const playerState = getPlayerState(player.name);
  const currentTags = player.getTags();
  const enabledConfigs = getEnabledTagConfigs();
  
  // Update tags and get changes
  const { added, removed, current } = playerState.updateTags(currentTags);
  
  // Stop sounds for removed tags IMMEDIATELY
  for (const removedTag of removed) {
    if (enabledConfigs.has(removedTag)) {
      playerState.stopSound(removedTag, player, 'tag removed');
    }
  }
  
  // Start sounds for newly added tags
  for (const addedTag of added) {
    const config = enabledConfigs.get(addedTag);
    if (config) {
      playerState.startSound(addedTag, config, player);
    }
  }
  
  // Handle priority system: if multiple tags are active, only play the highest priority
  const activeTags = [...current].filter(tag => enabledConfigs.has(tag));
  if (activeTags.length > 1) {
    // Sort by priority (lower number = higher priority)
    const sortedTags = activeTags.sort((a, b) => {
      const priorityA = enabledConfigs.get(a).PRIORITY || 100;
      const priorityB = enabledConfigs.get(b).PRIORITY || 100;
      return priorityA - priorityB;
    });
    
    const highestPriorityTag = sortedTags[0];
    
    // Stop all sounds except the highest priority one
    for (const tag of sortedTags.slice(1)) {
      if (playerState.activeSounds.has(tag)) {
        playerState.stopSound(tag, player, 'lower priority');
      }
    }
    
    // Ensure highest priority sound is playing
    const config = enabledConfigs.get(highestPriorityTag);
    if (config && !playerState.activeSounds.has(highestPriorityTag)) {
      playerState.startSound(highestPriorityTag, config, player);
    }
  }
}

/**
 * Debug function to show all player states
 */
function debugAllPlayerStates() {
  if (CONFIG.DEBUG.ALL || CONFIG.DEBUG.TAGGING) {
    log('=== PLAYER SOUND STATES DEBUG ===', 'TAGGING');
    for (const [playerName, state] of playerStates.entries()) {
      const debugInfo = state.getDebugInfo();
      log(`Player: ${playerName}`, 'TAGGING');
      log(`  Tags: [${debugInfo.currentTags.join(', ')}]`, 'TAGGING');
      log(`  Active Sounds: ${JSON.stringify(debugInfo.activeSounds, null, 2)}`, 'TAGGING');
      log(`  Play Counters: ${JSON.stringify(debugInfo.playCounters, null, 2)}`, 'TAGGING');
    }
    log('=== END DEBUG ===', 'TAGGING');
  }
}

// Event handlers
world.afterEvents.playerSpawn.subscribe((event) => {
  const player = event.player;
  log(`Player spawned: ${player.name}. Initializing new tag sound system.`, 'TAGGING');
  
  // Initialize after a short delay to ensure player is fully loaded
  system.runTimeout(() => {
    processPlayerTags(player);
  }, 20);
});

world.beforeEvents.playerLeave.subscribe((event) => {
  const player = event.player;
  const playerState = playerStates.get(player.name);
  
  if (playerState) {
    playerState.stopAllSounds(player);
    cleanupPlayerState(player.name);
  }
  
  world.sendMessage(`${player.name} has left the game.`);
});

// Main processing interval - check all players every TAG_CHECK_INTERVAL ticks
let debugInterval = 0;
system.runInterval(() => {
  try {
    world.getPlayers().forEach(player => {
      processPlayerTags(player);
    });
    
    // Debug every 100 intervals to reduce spam
    debugInterval++;
    if (debugInterval >= 100) {
      debugAllPlayerStates();
      debugInterval = 0;
    }
  } catch (error) {
    log(`Error in main processing interval: ${error.message}`, 'TAGGING');
  }
}, CONFIG.TAG_CHECK_INTERVAL || 10);

/**
 * Export functions for external use
 */
export function registerTagSoundSystem() {
  log('Redesigned tag-based sound system registered successfully.', 'TAGGING');
}

export function getPlayerSoundState(playerName) {
  return playerStates.get(playerName);
}

export function forceStopAllSounds(playerName) {
  const player = world.getPlayers().find(p => p.name === playerName);
  const playerState = playerStates.get(playerName);
  
  if (player && playerState) {
    playerState.stopAllSounds(player);
    log(`Force stopped all sounds for player "${playerName}"`, 'TAGGING');
  }
}

export function resetPlayerCooldowns(playerName) {
  const playerState = playerStates.get(playerName);
  if (playerState) {
    playerState.resetAllCooldowns();
    return true;
  }
  return false;
}

export function resetSoundCooldown(playerName, soundName) {
  const playerState = playerStates.get(playerName);
  if (playerState) {
    return playerState.resetCooldown(soundName);
  }
  return false;
}

export function getPlayerCooldowns(playerName) {
  const playerState = playerStates.get(playerName);
  if (playerState) {
    return Object.fromEntries(playerState.soundCooldowns);
  }
  return {};
}

export { debugAllPlayerStates };

// Auto-register the system
registerTagSoundSystem();