"""Core music player module with audio playback functionality.

This module provides the core audio playback functionality for the music player
application, including audio player, playlist management, and configuration.
"""

from __future__ import annotations

import json
import logging
import threading
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from functools import cached_property
from pathlib import Path
from typing import ClassVar

from mutagen import File
from mutagen.id3 import ID3NoHeaderError

from pytola.multimedia.musicplayer.optimized_audio_player import AudioTrack, PlaybackState
from pytola.multimedia.musicplayer.optimized_audio_player import OptimizedAudioPlayer as AudioPlayer
from pytola.multimedia.musicplayer.playlist_persistence import PlaylistPersistenceManager

logger = logging.getLogger(__name__)


class PlaybackMode(Enum):
    """Enumeration of playback modes."""

    SEQUENTIAL = "sequential"  # 顺序播放
    SHUFFLE = "shuffle"  # 随机播放
    SINGLE_LOOP = "single_loop"  # 单曲循环
    INFINITE_LOOP = "infinite_loop"  # 列表循环

    def get_display_name(self) -> str:
        """Get display name for the playback mode."""
        names = {
            PlaybackMode.SEQUENTIAL: "顺序播放",
            PlaybackMode.SHUFFLE: "随机播放",
            PlaybackMode.SINGLE_LOOP: "单曲循环",
            PlaybackMode.INFINITE_LOOP: "列表循环",
        }
        return names.get(self, self.value)

    def get_symbol(self) -> str:
        """Get symbol for the playback mode."""
        symbols = {
            PlaybackMode.SEQUENTIAL: "➡️",
            PlaybackMode.SHUFFLE: "🔀",
            PlaybackMode.SINGLE_LOOP: "🔂",
            PlaybackMode.INFINITE_LOOP: "🔁",
        }
        return symbols.get(self, "🎵")


@dataclass
class MusicPlayerConfig:
    """Configuration class for music player with persistent storage.

    Manages user preferences including window geometry, volume settings,
    theme preferences, and recent folders. Configuration is automatically
    saved to ~/.pytola/musicplayer.json
    """

    # Window geometry settings
    window_width: int = 1000
    window_height: int = 700
    window_x: int = 100
    window_y: int = 100

    # Audio settings
    volume: float = 0.7
    last_volume: float = 0.7

    # Playback settings
    repeat_mode: str = "none"  # none, one, all
    shuffle_mode: bool = False

    # UI settings
    theme: str = "light"  # light, dark
    show_album_art: bool = True
    show_visualizer: bool = False

    # Recent items
    recent_folders: list[str] = field(default_factory=list)
    recent_playlists: list[str] = field(default_factory=list)

    # Last played
    last_playlist: str = ""
    last_track_index: int = -1
    last_position: float = 0.0

    def __post_init__(self) -> None:
        """Post-initialization hook for configuration validation."""
        # Validate volume range
        self.volume = max(0.0, min(1.0, self.volume))
        self.last_volume = max(0.0, min(1.0, self.last_volume))

        # Limit recent items to reasonable amounts
        self.recent_folders = self.recent_folders[-10:]  # Keep last 10
        self.recent_playlists = self.recent_playlists[-5:]  # Keep last 5


@dataclass
class Playlist:
    """Data class representing a music playlist."""

    name: str
    tracks: list[AudioTrack] = field(default_factory=list)
    created_at: str = ""
    modified_at: str = ""

    @cached_property
    def total_duration(self) -> float:
        """Calculate total duration of all tracks in playlist."""
        return sum(track.duration for track in self.tracks)

    @cached_property
    def track_count(self) -> int:
        """Get number of tracks in playlist."""
        return len(self.tracks)

    def add_track(self, track: AudioTrack) -> None:
        """Add a track to the playlist."""
        self.tracks.append(track)
        self._invalidate_cache()

    def remove_track(self, index: int) -> bool:
        """Remove track at specified index.

        Args:
            index: Index of track to remove

        Returns
        -------
            True if track removed successfully, False if index invalid
        """
        if 0 <= index < len(self.tracks):
            self.tracks.pop(index)
            self._invalidate_cache()
            return True
        return False

    def add_track_to_playlist(self, playlist_name: str, track: AudioTrack) -> bool:
        """Add a track to specified playlist.

        Args:
            playlist_name: Name of playlist to add track to
            track: AudioTrack object to add

        Returns
        -------
            True if track added successfully, False if playlist not found
        """
        playlist = self._playlists.get(playlist_name)
        if playlist:
            playlist.add_track(track)

            # Also add to persistence manager
            self._persistence_manager.add_track_to_playlist(playlist_name, track)

            logger.info(f"Added track '{track.title}' to playlist '{playlist_name}'")
            return True
        return False

    def save_current_playlist(self, playlist_name: str) -> bool:
        """Save current playlist state to persistent storage.

        Args:
            playlist_name: Name of playlist to save

        Returns
        -------
            True if saved successfully, False if playlist not found
        """
        playlist = self._playlists.get(playlist_name)
        if not playlist:
            logger.error(f"Playlist '{playlist_name}' not found for saving")
            return False

        try:
            # Create or update saved playlist
            saved_playlist = self._persistence_manager.get_playlist(playlist_name)
            if not saved_playlist:
                saved_playlist = self._persistence_manager.create_playlist(
                    playlist_name,
                )
            else:
                # Clear existing tracks
                saved_playlist.clear()

            # Add current tracks
            for track in playlist.tracks:
                saved_playlist.add_track(track)

            # Update metadata
            saved_playlist.created_at = playlist.created_at
            saved_playlist.modified_at = playlist.modified_at

            # Save to file
            self._persistence_manager.save_playlists()

            logger.info(f"Saved playlist '{playlist_name}' to persistent storage")
            return True

        except Exception as e:
            logger.exception(f"Failed to save playlist '{playlist_name}': {e}")
            return False

    def load_playlist_from_persistence(self, playlist_name: str) -> bool:
        """Load playlist from persistent storage.

        Args:
            playlist_name: Name of playlist to load

        Returns
        -------
            True if loaded successfully, False if playlist not found
        """
        try:
            saved_playlist = self._persistence_manager.get_playlist(playlist_name)
            if not saved_playlist:
                logger.error(f"Saved playlist '{playlist_name}' not found")
                return False

            # Create playlist in memory
            playlist = Playlist(
                name=saved_playlist.name,
                created_at=saved_playlist.created_at,
                modified_at=saved_playlist.modified_at,
            )

            # Add tracks
            for saved_track in saved_playlist.tracks:
                try:
                    track = AudioTrack(
                        file_path=Path(saved_track.file_path),
                        title=saved_track.title,
                        artist=saved_track.artist,
                        album=saved_track.album,
                        duration=saved_track.duration,
                        file_size=saved_track.file_size,
                    )
                    playlist.add_track(track)
                except Exception as e:
                    logger.warning(f"Failed to load track {saved_track.title}: {e}")
                    continue

            self._playlists[playlist_name] = playlist
            logger.info(f"Loaded playlist '{playlist_name}' from persistent storage")
            return True

        except Exception as e:
            logger.exception(f"Failed to load playlist '{playlist_name}': {e}")
            return False

    def move_track(self, from_index: int, to_index: int) -> bool:
        """Move track from one position to another.

        Args:
            from_index: Source index
            to_index: Destination index

        Returns
        -------
            True if move successful, False if indices invalid
        """
        if 0 <= from_index < len(self.tracks) and 0 <= to_index < len(self.tracks):
            track = self.tracks.pop(from_index)
            self.tracks.insert(to_index, track)
            self._invalidate_cache()
            return True
        return False

    def clear(self) -> None:
        """Remove all tracks from playlist."""
        self.tracks.clear()
        self._invalidate_cache()

    def _invalidate_cache(self) -> None:
        """Invalidate cached properties when playlist changes."""
        if "total_duration" in self.__dict__:
            del self.__dict__["total_duration"]
        if "track_count" in self.__dict__:
            del self.__dict__["track_count"]

    def __iter__(self):
        """Make playlist iterable over tracks."""
        return iter(self.tracks)

    def __len__(self) -> int:
        """Get playlist length."""
        return len(self.tracks)


class PlaylistManager:
    """Manager class for handling playlist operations and file scanning.

    Integrates with PlaylistPersistenceManager for persistent storage
    following coding standards.
    """

    def __init__(
        self,
        persistence_manager: PlaylistPersistenceManager | None = None,
    ) -> None:
        """Initialize playlist manager.

        Args:
            persistence_manager: Optional persistence manager for saving/loading playlists
        """
        self._playlists: dict[str, Playlist] = {}
        self._recent_folders: list[Path] = []
        self._supported_extensions = {".mp3", ".wav", ".flac", ".ogg", ".m4a"}
        self._persistence_manager = persistence_manager or PlaylistPersistenceManager()

        # Load saved playlists on initialization
        self._load_saved_playlists()

    def _load_saved_playlists(self) -> None:
        """Load saved playlists from persistence manager."""
        try:
            saved_playlists = self._persistence_manager.get_all_playlists()
            for name, saved_playlist in saved_playlists.items():
                # Convert SavedPlaylist to Playlist
                playlist = Playlist(
                    name=saved_playlist.name,
                    created_at=saved_playlist.created_at,
                    modified_at=saved_playlist.modified_at,
                )

                # Convert SavedPlaylistTrack to AudioTrack
                for saved_track in saved_playlist.tracks:
                    try:
                        track = AudioTrack(
                            file_path=Path(saved_track.file_path),
                            title=saved_track.title,
                            artist=saved_track.artist,
                            album=saved_track.album,
                            duration=saved_track.duration,
                            file_size=saved_track.file_size,
                        )
                        playlist.add_track(track)
                    except Exception as e:
                        logger.warning(f"Failed to load track {saved_track.title}: {e}")
                        continue

                self._playlists[name] = playlist

            logger.info(f"Loaded {len(saved_playlists)} saved playlists")

        except Exception as e:
            logger.exception(f"Failed to load saved playlists: {e}")

    @property
    def playlists(self) -> dict[str, Playlist]:
        """Get all playlists."""
        return self._playlists.copy()

    @property
    def recent_folders(self) -> list[Path]:
        """Get recently accessed folders."""
        return self._recent_folders.copy()

    def create_playlist(self, name: str) -> Playlist:
        """Create a new playlist.

        Args:
            name: Playlist name

        Returns
        -------
            Created playlist object
        """
        if name in self._playlists:
            logger.warning(f"Playlist '{name}' already exists")
            return self._playlists[name]

        playlist = Playlist(name=name)
        self._playlists[name] = playlist

        # Also create in persistence manager
        self._persistence_manager.create_playlist(name)

        logger.info(f"Created playlist: {name}")
        return playlist

    def delete_playlist(self, name: str) -> bool:
        """Delete a playlist.

        Args:
            name: Playlist name to delete

        Returns
        -------
            True if deleted successfully, False if playlist not found
        """
        if name in self._playlists:
            del self._playlists[name]

            # Also delete from persistence manager
            self._persistence_manager.delete_playlist(name)

            logger.info(f"Deleted playlist: {name}")
            return True
        return False

    def get_playlist(self, name: str) -> Playlist | None:
        """Get playlist by name.

        Args:
            name: Playlist name

        Returns
        -------
            Playlist object or None if not found
        """
        return self._playlists.get(name)

    def scan_folder_for_tracks(self, folder_path: Path) -> list[AudioTrack]:
        """Scan folder recursively for audio files and extract metadata.

        Args:
            folder_path: Path to folder to scan

        Returns
        -------
            List of AudioTrack objects found in folder
        """
        if not folder_path.exists() or not folder_path.is_dir():
            logger.error(f"Folder does not exist or is not a directory: {folder_path}")
            return []

        tracks = []
        try:
            for file_path in folder_path.rglob("*"):
                if file_path.is_file() and file_path.suffix.lower() in self._supported_extensions:
                    track = self._extract_track_metadata(file_path)
                    if track:
                        tracks.append(track)

            logger.info(f"Found {len(tracks)} audio files in {folder_path}")
            self._add_to_recent_folders(folder_path)

        except Exception as e:
            logger.exception(f"Error scanning folder {folder_path}: {e}")

        return tracks

    def _extract_track_metadata(self, file_path: Path) -> AudioTrack | None:
        """Extract metadata from audio file.

        Args:
            file_path: Path to audio file

        Returns
        -------
            AudioTrack object with extracted metadata or None if failed
        """
        try:
            # Get basic file information
            stat = file_path.stat()
            file_size = stat.st_size

            # Extract metadata using mutagen
            try:
                audio_file = File(str(file_path))
                if audio_file is None:
                    return None

                # Extract common metadata fields
                title = self._get_metadata_value(audio_file, ["TIT2", "title", "TITLE"])
                artist = self._get_metadata_value(
                    audio_file,
                    ["TPE1", "artist", "ARTIST"],
                )
                album = self._get_metadata_value(audio_file, ["TALB", "album", "ALBUM"])

                # Get duration
                duration = float(audio_file.info.length) if hasattr(audio_file.info, "length") else 0.0

            except ID3NoHeaderError:
                # File has no ID3 tags, use filename as title
                title = file_path.stem
                artist = "Unknown Artist"
                album = "Unknown Album"
                duration = 0.0

            # Fallback values
            if not title:
                title = file_path.stem
            if not artist:
                artist = "Unknown Artist"
            if not album:
                album = "Unknown Album"

            return AudioTrack(
                file_path=file_path,
                title=title,
                artist=artist,
                album=album,
                duration=duration,
                file_size=file_size,
            )

        except Exception as e:
            logger.exception(f"Error extracting metadata from {file_path}: {e}")
            return None

    def _get_metadata_value(self, audio_file, keys: list[str]) -> str:
        """Get metadata value from audio file using various possible keys.

        Args:
            audio_file: Mutagen audio file object
            keys: List of possible metadata keys to try

        Returns
        -------
            Metadata value as string, or empty string if not found
        """
        for key in keys:
            if key in audio_file:
                value = audio_file[key]
                if hasattr(value, "text") and value.text:
                    return str(value.text[0])
                return str(value)
        return ""

    def add_folder_to_playlist(self, playlist_name: str, folder_path: Path) -> int:
        """Add all tracks from folder to specified playlist.

        Args:
            playlist_name: Name of target playlist
            folder_path: Path to folder containing audio files

        Returns
        -------
            Number of tracks added to playlist
        """
        playlist = self.get_playlist(playlist_name)
        if playlist is None:
            playlist = self.create_playlist(playlist_name)

        tracks = self.scan_folder_for_tracks(folder_path)
        added_count = 0

        for track in tracks:
            playlist.add_track(track)
            added_count += 1

        logger.info(f"Added {added_count} tracks to playlist '{playlist_name}'")
        return added_count

    def _add_to_recent_folders(self, folder_path: Path) -> None:
        """Add folder to recent folders list, maintaining maximum of 10 entries."""
        if folder_path in self._recent_folders:
            self._recent_folders.remove(folder_path)

        self._recent_folders.insert(0, folder_path)

        # Keep only last 10 folders
        if len(self._recent_folders) > 10:
            self._recent_folders = self._recent_folders[:10]

    def get_supported_extensions(self) -> set[str]:
        """Get set of supported audio file extensions."""
        return self._supported_extensions.copy()

    def is_supported_file(self, file_path: Path) -> bool:
        """Check if file is a supported audio format.

        Args:
            file_path: Path to file to check

        Returns
        -------
            True if file extension is supported, False otherwise
        """
        return file_path.suffix.lower() in self._supported_extensions


class ConfigManager:
    """Manager class for music player configuration persistence.

    Handles loading, saving, and updating of user configuration settings.
    Uses dataclass pattern for type safety and automatic serialization.
    """

    CONFIG_FILE: ClassVar[Path] = Path.home() / ".pytola" / "musicplayer.json"
    DEFAULT_CONFIG: ClassVar[MusicPlayerConfig] = MusicPlayerConfig()

    def __init__(self, config_file: Path | None = None) -> None:
        """Initialize configuration manager with optional custom config file.

        Args:
            config_file: Optional custom path for configuration file.
                       If None, uses default location ~/.pytola/musicplayer.json
        """
        if config_file is not None:
            self.config_file = config_file
        else:
            self.config_file = self.CONFIG_FILE

        # Create a copy of default config to avoid sharing state between instances
        self._config: MusicPlayerConfig = MusicPlayerConfig()
        self._load_config()

    def get_config(self) -> MusicPlayerConfig:
        """Get current configuration object.

        Returns
        -------
            Current MusicPlayerConfig instance
        """
        return self._config

    def update_config(self, **kwargs) -> None:
        """Update configuration with new values.

        Args:
            **kwargs: Configuration key-value pairs to update
        """
        for key, value in kwargs.items():
            if hasattr(self._config, key):
                setattr(self._config, key, value)
            else:
                logger.warning(f"Unknown configuration key: {key}")

        self.save_config()

    def _load_config(self) -> None:
        """Load configuration from file, falling back to defaults."""
        if not self.config_file.exists():
            logger.info(
                f"Configuration file not found, using defaults: {self.config_file}",
            )
            self._config = MusicPlayerConfig()
            return

        try:
            with Path(self.config_file).open(encoding="utf-8") as f:
                config_data = json.load(f)

            # Create config object with loaded data
            self._config = MusicPlayerConfig(**config_data)
            logger.info(f"Configuration loaded from: {self.config_file}")

        except (json.JSONDecodeError, TypeError, ValueError) as e:
            logger.exception(
                f"Failed to load configuration from {self.config_file}: {e}",
            )
            logger.info("Using default configuration")
            self._config = MusicPlayerConfig()

    def save_config(self) -> None:
        """Save current configuration to file."""
        try:
            # Ensure config directory exists
            self.config_file.parent.mkdir(parents=True, exist_ok=True)

            # Convert config to dictionary for JSON serialization
            config_dict = {
                "window_width": self._config.window_width,
                "window_height": self._config.window_height,
                "window_x": self._config.window_x,
                "window_y": self._config.window_y,
                "volume": self._config.volume,
                "last_volume": self._config.last_volume,
                "repeat_mode": self._config.repeat_mode,
                "shuffle_mode": self._config.shuffle_mode,
                "theme": self._config.theme,
                "show_album_art": self._config.show_album_art,
                "show_visualizer": self._config.show_visualizer,
                "recent_folders": self._config.recent_folders,
                "recent_playlists": self._config.recent_playlists,
                "last_playlist": self._config.last_playlist,
                "last_track_index": self._config.last_track_index,
                "last_position": self._config.last_position,
            }

            # Write configuration to file
            with Path(self.config_file).open("w", encoding="utf-8") as f:
                json.dump(config_dict, f, indent=4, ensure_ascii=False)

            logger.debug(f"Configuration saved to: {self.config_file}")

        except Exception as e:
            logger.exception(f"Failed to save configuration to {self.config_file}: {e}")

    def add_recent_folder(self, folder_path: str) -> None:
        """Add folder to recent folders list.

        Args:
            folder_path: Path to add to recent folders
        """
        if folder_path in self._config.recent_folders:
            self._config.recent_folders.remove(folder_path)

        self._config.recent_folders.insert(0, folder_path)

        # Keep only last 10 folders
        self._config.recent_folders = self._config.recent_folders[:10]
        self.save_config()

    def add_recent_playlist(self, playlist_name: str) -> None:
        """Add playlist to recent playlists list.

        Args:
            playlist_name: Name of playlist to add
        """
        if playlist_name in self._config.recent_playlists:
            self._config.recent_playlists.remove(playlist_name)

        self._config.recent_playlists.insert(0, playlist_name)

        # Keep only last 5 playlists
        self._config.recent_playlists = self._config.recent_playlists[:5]
        self.save_config()

    def get_recent_folders(self) -> list[str]:
        """Get list of recent folders.

        Returns
        -------
            List of recent folder paths
        """
        return self._config.recent_folders.copy()

    def get_recent_playlists(self) -> list[str]:
        """Get list of recent playlists.

        Returns
        -------
            List of recent playlist names
        """
        return self._config.recent_playlists.copy()

    @cached_property
    def config_directory(self) -> Path:
        """Get configuration directory path.

        Returns
        -------
            Path to configuration directory
        """
        return self.config_file.parent

    def reset_to_defaults(self) -> None:
        """Reset configuration to default values."""
        self._config = MusicPlayerConfig()
        self.save_config()
        logger.info("Configuration reset to defaults")

    def backup_config(self, backup_path: Path | None = None) -> bool:
        """Create backup of current configuration.

        Args:
            backup_path: Optional custom backup path

        Returns
        -------
            True if backup created successfully, False otherwise
        """
        try:
            if backup_path is None:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                backup_path = self.config_file.with_name(
                    f"musicplayer_backup_{timestamp}.json",
                )

            backup_path.parent.mkdir(parents=True, exist_ok=True)

            # Create backup directly from current config in memory
            # This avoids triggering additional datetime calls during save_config()
            config_dict = {
                "window_width": self._config.window_width,
                "window_height": self._config.window_height,
                "window_x": self._config.window_x,
                "window_y": self._config.window_y,
                "volume": self._config.volume,
                "last_volume": self._config.last_volume,
                "repeat_mode": self._config.repeat_mode,
                "shuffle_mode": self._config.shuffle_mode,
                "theme": self._config.theme,
                "show_album_art": self._config.show_album_art,
                "show_visualizer": self._config.show_visualizer,
                "recent_folders": self._config.recent_folders,
                "recent_playlists": self._config.recent_playlists,
                "last_playlist": self._config.last_playlist,
                "last_track_index": self._config.last_track_index,
                "last_position": self._config.last_position,
            }

            with Path(backup_path).open("w", encoding="utf-8") as backup:
                json.dump(config_dict, backup, indent=4, ensure_ascii=False)

            logger.info(f"Configuration backed up to: {backup_path}")
            return True

        except Exception as e:
            logger.exception(f"Failed to backup configuration: {e}")
            return False


class MusicPlayerCore:
    """Core music player class encapsulating all playback functionality.

    This class provides a unified interface for audio playback, playlist
    management, and configuration handling without any GUI dependencies.
    """

    def __init__(self) -> None:
        """Initialize core music player components with preloading optimization."""
        self.audio_player = AudioPlayer()
        self.playlist_manager = PlaylistManager()
        self.config_manager = ConfigManager()
        self._current_playlist_name: str = "Default"
        self._current_track_index: int = -1
        self._playback_mode: PlaybackMode = PlaybackMode.INFINITE_LOOP  # 默认列表循环
        self._shuffle_indices: list[int] = []  # 随机播放索引列表
        self._current_shuffle_index: int = 0  # 当前随机播放索引

        # 预加载相关属性
        self._preload_cache = {}  # 缓存预加载的音频数据
        self._preload_thread = None
        self._preload_queue = []  # 预加载队列
        self._preload_enabled = True

        # 智能缓存管理
        self._cache_stats = {"hits": 0, "misses": 0, "evictions": 0}
        self._access_history = {}  # 访问历史记录

    @property
    def current_playlist_name(self) -> str:
        """Get current playlist name."""
        return self._current_playlist_name

    @property
    def current_track_index(self) -> int:
        """Get current track index."""
        return self._current_track_index

    @property
    def playback_mode(self) -> PlaybackMode:
        """Get current playback mode."""
        return self._playback_mode

    @playback_mode.setter
    def playback_mode(self, mode: PlaybackMode) -> None:
        """Set playback mode and handle mode-specific initialization."""
        old_mode = self._playback_mode
        self._playback_mode = mode

        # Re-generate shuffle indices if switching to shuffle mode
        if mode == PlaybackMode.SHUFFLE and old_mode != PlaybackMode.SHUFFLE:
            self._generate_shuffle_indices()

        logger.info(f"Playback mode changed to: {mode.get_display_name()}")

    def _generate_shuffle_indices(self) -> None:
        """Generate shuffled indices for random playback mode."""
        tracks = self.get_current_playlist_tracks()
        if not tracks:
            self._shuffle_indices = []
            self._current_shuffle_index = 0
            return

        import random

        self._shuffle_indices = list(range(len(tracks)))
        random.shuffle(self._shuffle_indices)
        self._current_shuffle_index = 0

        logger.info(
            f"Generated {len(self._shuffle_indices)} shuffled indices for random playback",
        )

    def _get_next_track_index(self) -> int | None:
        """Get the next track index based on current playback mode."""
        tracks = self.get_current_playlist_tracks()
        if not tracks or self._current_track_index < 0:
            return None

        current_index = self._current_track_index
        track_count = len(tracks)

        if self._playback_mode == PlaybackMode.SEQUENTIAL:
            # Sequential playback: next song
            if current_index < track_count - 1:
                next_index = current_index + 1
                # 预加载下一首歌曲
                self._preload_next_track(next_index)
                return next_index
            return None

        if self._playback_mode == PlaybackMode.SHUFFLE:
            # Shuffle playback: get next random index
            if not self._shuffle_indices or self._current_shuffle_index >= len(self._shuffle_indices) - 1:
                self._generate_shuffle_indices()

            self._current_shuffle_index += 1
            if self._current_shuffle_index < len(self._shuffle_indices):
                next_index = self._shuffle_indices[self._current_shuffle_index]
                # 预加载下一首歌曲
                self._preload_next_track(next_index)
                return next_index
            return None

        if self._playback_mode == PlaybackMode.SINGLE_LOOP:
            # Single song loop: repeat the current song
            return current_index

        if self._playback_mode == PlaybackMode.INFINITE_LOOP:
            # Circular list loop: return to the beginning of the list
            if current_index < track_count - 1:
                next_index = current_index + 1
                # 预加载下一首歌曲
                self._preload_next_track(next_index)
                return next_index
            next_index = 0  # 回到第一首
            # 预加载下一首歌曲
            self._preload_next_track(next_index)
            return next_index

        return None

    def load_folder_to_playlist(self, folder_path: Path) -> int:
        """Load all audio files from folder to current playlist.

        Args:
            folder_path: Path to folder containing audio files

        Returns
        -------
            Number of tracks added to playlist
        """
        # Clear current playlist
        self.clear_playlist()

        # Add tracks from folder
        track_count = self.playlist_manager.add_folder_to_playlist(
            self._current_playlist_name,
            folder_path,
        )

        if track_count > 0:
            self.config_manager.add_recent_folder(str(folder_path))
            logger.info(f"Loaded {track_count} tracks from {folder_path.name}")

        return track_count

    def clear_playlist(self) -> None:
        """Clear current playlist."""
        playlist = self.playlist_manager.get_playlist(self._current_playlist_name)
        if playlist:
            playlist.clear()
        self._current_track_index = -1
        self.audio_player.stop()

    def play_current_track(self) -> bool:
        """Play currently selected track.

        Returns
        -------
            True if playback started successfully, False otherwise
        """
        playlist = self.playlist_manager.get_playlist(self._current_playlist_name)
        if not playlist or len(playlist) == 0:
            logger.warning("No tracks in playlist")
            return False

        # Play first track if no track selected
        self._current_track_index = max(self._current_track_index, 0)

        return self.play_track_at_index(self._current_track_index)

    def play_track_at_index(self, index: int) -> bool:
        """Play track at specified index with asynchronous loading.

        Args:
            index: Track index to play

        Returns
        -------
            True if playback started successfully, False otherwise
        """
        playlist = self.playlist_manager.get_playlist(self._current_playlist_name)
        if not playlist or index >= len(playlist):
            return False

        track = playlist.tracks[index]

        # Asynchronously load and play the track to avoid UI blocking
        def async_load_and_play() -> None:
            try:
                if self.audio_player.load_track(track):
                    if self.audio_player.play():
                        self._current_track_index = index
                        logger.info(f"Playing: {track.title}")

                        # 使用信号槽机制监听播放状态变化
                        self.audio_player.playback_finished.connect(
                            self._on_track_finished,
                        )
                        self.audio_player.state_changed.connect(
                            self._on_playback_state_changed,
                        )
                    else:
                        logger.error("Failed to start playback")
                else:
                    logger.error("Failed to load track")
            except Exception as e:
                logger.exception(f"Async play error: {e}")

        # 在后台线程中执行加载和播放
        import threading

        play_thread = threading.Thread(target=async_load_and_play, daemon=True)
        play_thread.start()

        # Immediately return True, keeping the UI responsive
        return True

    def _on_playback_state_changed(self, state: PlaybackState) -> None:
        """Handle playback state changes."""
        # 可以在这里添加状态变化的处理逻辑

    def _preload_next_track(self, index: int) -> None:
        """Preload next track to improve playback response speed."""
        if not self._preload_enabled:
            return

        tracks = self.get_current_playlist_tracks()
        if not tracks or index >= len(tracks):
            return

        next_track = tracks[index]
        track_key = str(next_track.file_path)

        if track_key in self._preload_cache:
            return

        # 在后台线程中预加载
        def preload_worker() -> None:
            try:
                # Use lightweight loading (only load first few seconds for quick startup)
                import numpy as np
                import soundfile as sf

                with sf.SoundFile(str(next_track.file_path)) as f:
                    # Only load first 30 seconds of data for quick preview
                    preview_frames = min(int(f.samplerate * 30), f.frames)
                    audio_data = f.read(frames=preview_frames, dtype="float32")

                    # Convert to stereo
                    if audio_data.ndim == 1:
                        audio_data = np.column_stack([audio_data, audio_data])

                    # Cache preloaded data
                    self._preload_cache[track_key] = {
                        "data": audio_data,
                        "sample_rate": f.samplerate,
                        "full_duration": next_track.duration,
                    }

                    logger.debug(
                        f"Preloaded track: {next_track.title} ({len(audio_data)} frames)",
                    )
            except Exception as e:
                logger.warning(f"Failed to preload track {next_track.title}: {e}")

        # 启动预加载线程
        preload_thread = threading.Thread(target=preload_worker, daemon=True)
        preload_thread.start()

    def _on_track_finished(self) -> None:
        """Handle track finished event based on playback mode."""
        next_index = self._get_next_track_index()

        if next_index is not None:
            # 根据播放模式播放下一首
            logger.info(
                f"Auto-playing next track (mode: {self._playback_mode.get_display_name()})",
            )
            self.play_track_at_index(next_index)
        else:
            # Playlist finished, stopping playback
            logger.info("Playlist finished, stopping playback")
            self.stop_playback()

    def pause_playback(self) -> None:
        """Pause current playback."""
        self.audio_player.pause()

    def resume_playback(self) -> bool:
        """Resume paused playback.

        Returns
        -------
            True if resumed successfully, False otherwise
        """
        return self.audio_player.resume()

    def stop_playback(self) -> None:
        """Stop playback."""
        self.audio_player.stop()
        self._current_track_index = -1

    def set_volume(self, volume: float) -> None:
        """Set playback volume.

        Args:
            volume: Volume level (0.0 to 1.0)
        """
        self.audio_player.volume = volume
        self.config_manager.update_config(volume=volume)

    def seek_position(self, position: float) -> bool:
        """Seek to specific position in current track.

        Args:
            position: Position in seconds

        Returns
        -------
            True if seek successful, False otherwise
        """
        return self.audio_player.seek(position)

    def get_current_playlist_tracks(self) -> list[AudioTrack]:
        """Get all tracks in current playlist.

        Returns
        -------
            List of AudioTrack objects
        """
        playlist = self.playlist_manager.get_playlist(self._current_playlist_name)
        if playlist:
            return playlist.tracks.copy()
        return []

    def get_track_info(self, index: int) -> AudioTrack | None:
        """Get track information by index.

        Args:
            index: Track index

        Returns
        -------
            AudioTrack object or None if index invalid
        """
        tracks = self.get_current_playlist_tracks()
        if 0 <= index < len(tracks):
            return tracks[index]
        return None

    def cycle_playback_mode(self) -> PlaybackMode:
        """Cycle to the next playback mode.

        Order: SEQUENTIAL -> SHUFFLE -> SINGLE_LOOP -> INFINITE_LOOP -> SEQUENTIAL

        Returns
        -------
            New playback mode
        """
        mode_order = [
            PlaybackMode.SEQUENTIAL,
            PlaybackMode.SHUFFLE,
            PlaybackMode.SINGLE_LOOP,
            PlaybackMode.INFINITE_LOOP,
        ]

        current_index = mode_order.index(self._playback_mode)
        next_index = (current_index + 1) % len(mode_order)
        self.playback_mode = mode_order[next_index]

        return self._playback_mode

    def get_mode_display_info(self) -> tuple[str, str]:
        """Get display information for current playback mode.

        Returns
        -------
            Tuple of (symbol, display_name)
        """
        return (
            self._playback_mode.get_symbol(),
            self._playback_mode.get_display_name(),
        )

    def cleanup(self) -> None:
        """Clean up resources."""
        self.audio_player.cleanup()


def main() -> None:
    """Run main entry point for console-based music player.

    This provides a simple command-line interface for basic music playback.
    For full GUI functionality, use musicplayer_gui.py
    """
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(levelname)s - %(message)s",
    )

    player = MusicPlayerCore()

    try:
        while True:
            command = input("> ").strip().split()
            if not command:
                continue

            cmd = command[0].lower()

            if cmd == "load" and len(command) > 1:
                folder_path = Path(command[1])
                if folder_path.exists():
                    player.load_folder_to_playlist(folder_path)
            elif cmd == "play":
                if player.play_current_track():
                    pass
            elif cmd == "pause":
                player.pause_playback()
            elif cmd == "stop":
                player.stop_playback()
            elif cmd in {"quit", "exit"}:
                break

    except KeyboardInterrupt:
        pass
    finally:
        player.cleanup()


# Export public API
__all__ = [
    "AudioPlayer",
    "AudioTrack",
    "ConfigManager",
    "MusicPlayerCore",
    "PlaybackMode",
    "PlaylistManager",
    "main",
]
