Why are new snippets not loading in when the app is in search mode. After I search, only 10 snippets show up, even when I scroll to the end. I do not see the loading indicator at the end of the card, and no additional snippets load in:

import UIKit
import AVFoundation
import CoreData
import SafariServices


// MARK: - MainViewController

class MainViewController: UIViewController {
    
    // MARK: - UI Properties
    
    private var libraryLayout: LibraryLayoutProtocol?
    private var isLoadingSnippets = false
    private var musicCollectionView: UICollectionView!
    
    private let loadingIndicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView(style: .medium)
        indicator.translatesAutoresizingMaskIntoConstraints = false
        indicator.hidesWhenStopped = true
        return indicator
    }()
    
    private lazy var libraryButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "books.vertical"), for: .normal)
        button.tintColor = .white
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(libraryButtonTapped), for: .touchUpInside)
        return button
    }()
    
    private lazy var leaderboardButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "trophy"), for: .normal)
        button.tintColor = .white
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(leaderboardButtonTapped), for: .touchUpInside)
        return button
    }()
    
    private var libraryView: UIView!
    private var isLibraryVisible = false
    private var libraryItems: [LibraryItem] = []
    private var currentlyPlayingItem: LibraryItem?
    private var isAudioPaused = false
    private var isSearchIconVisible = true
    
    private var authLayout: AuthLayout!
    private var isAuthLayoutVisible = false
    
    private var leaderboardLayout: LeaderboardLayout!
    private var isLeaderboardVisible = false
    
    
    private lazy var userIconButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "person"), for: .normal)
        button.tintColor = .white
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(userIconButtonTapped), for: .touchUpInside)
        return button
    }()

    private var artistLayout: ArtistLayout!
    private var isArtistLayoutVisible = false
    
    private var cachedArtistData: ArtistData?
    private let artistDataQueue = DispatchQueue(label: "com.app.artistDataQueue")
    
    private lazy var searchIconButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "magnifyingglass"), for: .normal)
        button.tintColor = .white
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(searchIconTapped), for: .touchUpInside)
        return button
    }()
    
    private lazy var librarySearchButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "magnifyingglass"), for: .normal)
        button.tintColor = .white
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(librarySearchButtonTapped), for: .touchUpInside)
        button.alpha = 0
        button.isHidden = true
        return button
    }()
    
    private lazy var searchBar: UISearchBar = {
        let searchBar = UISearchBar()
        searchBar.translatesAutoresizingMaskIntoConstraints = false
        searchBar.searchBarStyle = .minimal
        searchBar.placeholder = "Search"
        searchBar.tintColor = .white
        searchBar.barTintColor = .white
        searchBar.backgroundColor = .black
        
        // Customize text field appearance
        if let textField = searchBar.value(forKey: "searchField") as? UITextField {
            textField.textColor = .white
            textField.backgroundColor = .black
            textField.layer.borderColor = UIColor.white.cgColor
            textField.layer.borderWidth = 1
            textField.layer.cornerRadius = 12
        }
        
        searchBar.isHidden = true
        return searchBar
    }()
    
    private lazy var librarySearchBar: UISearchBar = {
        let searchBar = UISearchBar()
        searchBar.translatesAutoresizingMaskIntoConstraints = false
        searchBar.searchBarStyle = .minimal
        searchBar.placeholder = "Search"
        searchBar.tintColor = .white
        searchBar.barTintColor = .white
        searchBar.backgroundColor = .black
        
        // Customize text field appearance
        if let textField = searchBar.value(forKey: "searchField") as? UITextField {
            textField.textColor = .white
            textField.backgroundColor = .black
            textField.layer.borderColor = UIColor.white.cgColor
            textField.layer.borderWidth = 1
            textField.layer.cornerRadius = 12
        }
        
        searchBar.isHidden = true
        searchBar.alpha = 0
        return searchBar
    }()
    
    private lazy var importButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "arrow.down"), for: .normal)
        button.tintColor = .white
        button.backgroundColor = .clear
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(importButtonTapped), for: .touchUpInside)
        
        let imageConfig = UIImage.SymbolConfiguration(pointSize: 16, weight: .regular)
        button.setPreferredSymbolConfiguration(imageConfig, forImageIn: .normal)
        
        return button
    }()
    
    private lazy var autoPlayButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Enable Auto-Play", for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
        button.setTitleColor(.white, for: .normal)
        button.backgroundColor = UIColor(white: 0.2, alpha: 0.8)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(autoPlayButtonTapped), for: .touchUpInside)
        return button
    }()
    
    private lazy var previousButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "chevron.left.circle.fill")?.withConfiguration(
            UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
        ), for: .normal)
        button.tintColor = .white
        button.alpha = 0
        button.isHidden = true
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(previousButtonTapped), for: .touchUpInside)
        return button
    }()
    
    private lazy var nextButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "chevron.right.circle.fill")?.withConfiguration(
            UIImage.SymbolConfiguration(pointSize: 24, weight: .medium)
        ), for: .normal)
        button.tintColor = .white
        button.alpha = 0
        button.isHidden = true
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
        return button
    }()

    // Add this property to track the state
    private var isAutoPlayEnabled = false
    
    private var filteredMusicSnippets: [MusicSnippet] = []
    private var filteredLibraryItems: [LibraryItem] = []
    private var isInSearchMode: Bool = false
    
    private var snippetBuffer: SnippetBuffer!
    
    private var isLoadingMore = false

    private var currentSearchPage = 1
    private var isLoadingSearchResults = false
    private var hasMoreSearchResults = true
    private var currentSearchQuery: String?
    private let maxSearchResults = 100
    private let resultsPerPage = 10


    
    // MARK: - Data Properties
    
    var musicSnippets: [MusicSnippet] = []
    
    // MARK: - Audio Properties
    
    private let audioPlayer = AudioPlayer()
    private let snippetManager = MusicSnippetManager()
    private var currentPlayingIndex: Int?
    
    // MARK: - Lifecycle Methods
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        setupCollectionView()
        setupLibraryButton()
        setupSearchBar()
        setupNavigationButtons()
        setupAutoPlayButton()
        setupNotifications()
        setupSwipeGestureRecognizer()
        setupTapGestureRecognizer()
        setupSearchIcon()
        setupUserIconButton()
        setupLeaderboardButton()
        setupLeaderboardLayout()
        setupArtistLayout()
        setupAuthLayout()
        setupLogoutObserver()
        setupLibrarySearchButton()
        setupLibrarySearchBar()
        

        // clearAllCaches()

        libraryLayout = LibraryLayout()
        print("DEBUG: libraryLayout initialized")
        
        setupLibraryView()
        print("DEBUG: setupLibraryView completed")
        
        configureLibraryLayoutCallbacks()
        
        setupLibraryLayout()
        print("DEBUG: setupLibraryLayout completed")
        
        searchBar.isHidden = true
        importButton.isHidden = true
        libraryButton.isHidden = false
        searchIconButton.isHidden = false
        
        audioPlayer.onPlaybackStateChanged = { [weak self] isPlaying in
            guard let self = self, let currentItem = self.currentlyPlayingItem else { return }
            self.updatePlayingState(for: currentItem, isPlaying: isPlaying)
        }
        
        audioPlayer.onNextTrackRequested = { [weak self] in
            guard let self = self,
                  let currentIndex = self.currentPlayingIndex else { return }
            self.moveToNextSnippet(from: currentIndex)
        }

        audioPlayer.onPreviousTrackRequested = { [weak self] in
            guard let self = self,
                  let currentIndex = self.currentPlayingIndex,
                  self.hasPreviousSnippet(from: currentIndex) else { return }
                      
            // Find the previous music snippet
            for index in (0..<currentIndex).reversed() {
                if self.filteredMusicSnippets[index].type == .music {
                    self.scrollToItem(at: index, animated: true)
                    break
                }
            }
        }
        
        if AuthManager.shared.isUserLoggedIn() {
            prefetchArtistData()
        }
        
        NotificationCenter.default.addObserver(self,
                                             selector: #selector(handleAppActive),
                                             name: UIApplication.didBecomeActiveNotification,
                                             object: nil)
        
        
        view.addSubview(loadingIndicator)
        setupLoadingIndicatorConstraints()
        
        musicCollectionView.register(LoadingIndicatorCell.self, forCellWithReuseIdentifier: "LoadingIndicatorCell")


        
        snippetBuffer = SnippetBuffer()
        snippetBuffer.onNewSnippetsAvailable = { [weak self] in
            self?.handleNewSnippets()
        }
        
//        snippetBuffer.onLoadingStateChanged = { [weak self] isLoading in
//            DispatchQueue.main.async {
//                if isLoading {
//                    self?.loadingIndicator.startAnimating()
//                } else {
//                    self?.loadingIndicator.stopAnimating()
//                }
//            }
//        }
        
//        snippetBuffer.onError = { [weak self] error in
//            DispatchQueue.main.async {
//                // Show an alert or toast to the user
//                self?.showErrorAlert("Failed to load new snippets. Please try again.")
//            }
//        }
        musicCollectionView.delegate = self

        
        registerNotifications()
    }
    
    private var hasInitiallyLoadedSnippets = false

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        if !hasInitiallyLoadedSnippets {
            fetchRandomMusicSnippets()
            hasInitiallyLoadedSnippets = true
        }
        audioPlayer.play()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // Pause playback if needed
        audioPlayer.pauseAudio()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    // MARK: - Setup Methods
    
    private func setupNavigationButtons() {
        view.addSubview(previousButton)
        view.addSubview(nextButton)
        
        NSLayoutConstraint.activate([
            previousButton.centerYAnchor.constraint(equalTo: musicCollectionView.centerYAnchor),
            previousButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            previousButton.widthAnchor.constraint(equalToConstant: 50),
            previousButton.heightAnchor.constraint(equalToConstant: 50),
            
            nextButton.centerYAnchor.constraint(equalTo: musicCollectionView.centerYAnchor),
            nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            nextButton.widthAnchor.constraint(equalToConstant: 50),
            nextButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupCollectionView() {
        let layout = CarouselLayout()
        musicCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        musicCollectionView.translatesAutoresizingMaskIntoConstraints = false
        musicCollectionView.backgroundColor = .clear
        view.addSubview(musicCollectionView)
        
        NSLayoutConstraint.activate([
            musicCollectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            musicCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            musicCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            musicCollectionView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1/3)
        ])
        
        musicCollectionView.dataSource = self
        musicCollectionView.delegate = self
        musicCollectionView.register(MusicSnippetCell.self, forCellWithReuseIdentifier: "MusicSnippetCell")
        musicCollectionView.register(SpecialCardCell.self, forCellWithReuseIdentifier: "SpecialCardCell")
        musicCollectionView.showsHorizontalScrollIndicator = false
    }
    
    private func setupLibraryButton() {
        view.addSubview(libraryButton)
        
        NSLayoutConstraint.activate([
            libraryButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
            libraryButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            libraryButton.widthAnchor.constraint(equalToConstant: 44),
            libraryButton.heightAnchor.constraint(equalToConstant: 44)
        ])
    }
    
    private func setupAutoPlayButton() {
        view.addSubview(autoPlayButton)
        
        NSLayoutConstraint.activate([
            autoPlayButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            autoPlayButton.topAnchor.constraint(equalTo: musicCollectionView.topAnchor, constant: 15),
            autoPlayButton.widthAnchor.constraint(equalToConstant: 160),
            autoPlayButton.heightAnchor.constraint(equalToConstant: 36)
        ])
    }
    
    private func setupLeaderboardLayout() {
        leaderboardLayout = LeaderboardLayout()
        view.addSubview(leaderboardLayout)
        leaderboardLayout.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            leaderboardLayout.topAnchor.constraint(equalTo: view.topAnchor),
            leaderboardLayout.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            leaderboardLayout.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            leaderboardLayout.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        leaderboardLayout.isHidden = true
        leaderboardLayout.delegate = self
    }
    
    private func setupLeaderboardButton () {
        view.addSubview(leaderboardButton)
        NSLayoutConstraint.activate([
            leaderboardButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
            leaderboardButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
            leaderboardButton.widthAnchor.constraint(equalToConstant: 44),
            leaderboardButton.heightAnchor.constraint(equalToConstant: 44)
        ])
    }
    
    private func setupAuthLayout() {
        authLayout = AuthLayout()
        view.addSubview(authLayout)
        authLayout.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            authLayout.topAnchor.constraint(equalTo: view.topAnchor),
            authLayout.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            authLayout.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            authLayout.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        authLayout.isHidden = true
        
        // Set the delegate
        authLayout.delegate = self
        
        // Existing callback assignments
        authLayout.onLoginSuccess = { [weak self] in
            self?.handleAuthSuccess()
        }
        
        authLayout.onRegisterSuccess = { [weak self] in
            self?.handleAuthSuccess()
        }
        
        authLayout.onBackTapped = { [weak self] in
            self?.toggleAuthLayout()
        }
    }

    private func setupSearchBar() {
        view.addSubview(searchBar)
        
        NSLayoutConstraint.activate([
            searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
            searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            searchBar.heightAnchor.constraint(equalToConstant: 40)
        ])
        
        searchBar.delegate = self
    }
    
    private func setupLibrarySearchBar() {
        view.addSubview(librarySearchBar)
        
        NSLayoutConstraint.activate([
            librarySearchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
            librarySearchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            librarySearchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            librarySearchBar.heightAnchor.constraint(equalToConstant: 40)
        ])
        
        librarySearchBar.delegate = self
    }
    
    private func setupSearchIcon() {
        view.addSubview(searchIconButton)
        NSLayoutConstraint.activate([
            searchIconButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
            searchIconButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            searchIconButton.widthAnchor.constraint(equalToConstant: 44),
            searchIconButton.heightAnchor.constraint(equalToConstant: 44)
        ])
    }
    
    private func setupLibraryView() {
        libraryView = UIView()
        libraryView.backgroundColor = .black
        libraryView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(libraryView)
        
        NSLayoutConstraint.activate([
            libraryView.topAnchor.constraint(equalTo: view.topAnchor, constant: 120),
            libraryView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            libraryView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            libraryView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -120)
        ])
        
        libraryView.isHidden = true
    }
    
    private func setupLibraryLayout() {
        libraryLayout?.onItemTapped = { [weak self] item, itemView in
            print("31")
            self?.handleLibraryItemTap(item: item, itemView: itemView)
        }
        
        libraryLayout?.onItemRenamed = { [weak self] item, newTitle in
            self?.handleLibraryItemRename(item: item, newTitle: newTitle)
        }
        
        libraryLayout?.onItemRemoved = { [weak self] item in
            self?.removeFromLibrary(item: item)
        }
        print("DEBUG: setupLibraryLayout completed")
    }
    
    private func setupUserIconButton() {
        view.addSubview(userIconButton)

        NSLayoutConstraint.activate([
            userIconButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
            userIconButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
            userIconButton.widthAnchor.constraint(equalToConstant: 44),
            userIconButton.heightAnchor.constraint(equalToConstant: 44)
        ])
    }

    private func setupArtistLayout() {
        artistLayout = ArtistLayout()
        view.addSubview(artistLayout)

        NSLayoutConstraint.activate([
            artistLayout.topAnchor.constraint(equalTo: view.topAnchor),
            artistLayout.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            artistLayout.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            artistLayout.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        artistLayout.isHidden = true
    }
    
    private func setupAPIKey() {
        APIKeyManager.shared.fetchAndStoreAPIKey { result in
            switch result {
            case .success:
                print("API key successfully fetched and stored")
            case .failure(let error):
                print("Failed to fetch API key: \(error)")
                // Handle error (e.g., retry, show alert to user)
            }
        }
    }
    
    private func setupLibrarySearchButton() {
        view.addSubview(librarySearchButton)
        
        NSLayoutConstraint.activate([
            librarySearchButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
            librarySearchButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            librarySearchButton.widthAnchor.constraint(equalToConstant: 44),
            librarySearchButton.heightAnchor.constraint(equalToConstant: 44)
        ])
    }
    
    private func setupNotifications() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(keyboardWillShow),
                                               name: UIResponder.keyboardWillShowNotification,
                                               object: nil)
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(keyboardWillHide),
                                               name: UIResponder.keyboardWillHideNotification,
                                               object: nil)
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(appDidBecomeActive),
                                               name: UIApplication.didBecomeActiveNotification,
                                               object: nil)
    }
    
    private func registerNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(libraryDidChange(_:)), name: .libraryDidChange, object: nil)
    }
    
    private func configureLibraryLayoutCallbacks() {
        libraryLayout?.onItemTapped = { [weak self] item, itemView in
            guard let self = self else { return }
            self.handleLibraryItemTap(item: item, itemView: itemView)
        }
        
        libraryLayout?.onItemRenamed = { [weak self] item, newTitle in

            self?.handleLibraryItemRename(item: item, newTitle: newTitle)
        }
        
        // Add this new callback implementation
        libraryLayout?.onViewArtistRequested = { [weak self] artistId in
            guard let self = self else { return }
            // Stop any current playback
            self.stopAudio()
            
            // Configure and show the artist layout with a back handler
            self.artistLayout.configure(
                artistId: artistId,
                presentationMode: .viewOnly,
                onBack: { [weak self] in
                    // Show library view again
                    self?.isLibraryVisible = true
                    self?.isArtistLayoutVisible = false
                    self?.artistLayout.hide()
                    self?.updateLibraryVisibility()
                    
                    // Ensure the libraryButton remains visible
                    self?.libraryButton.isHidden = false
                    UIView.animate(withDuration: 0.15) {
                        self?.libraryButton.alpha = 1
                    }
                }
            )
            
            // Hide library view and show artist layout
            self.isLibraryVisible = false
            self.updateLibraryVisibility()
            self.isArtistLayoutVisible = true
            self.artistLayout.show()
            
            // **Update UI elements to hide both buttons**
            UIView.animate(withDuration: 0) {
                self.libraryButton.alpha = 0
                self.searchIconButton.alpha = 0
            } completion: { _ in
                self.libraryButton.isHidden = true
                self.searchIconButton.isHidden = true
            }
        }
    }
    
    private func setupLoadingIndicatorConstraints() {
        NSLayoutConstraint.activate([
            loadingIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            loadingIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    private func setupSwipeGestureRecognizer() {
        let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture(_:)))
        swipeGesture.direction = .down
        view.addGestureRecognizer(swipeGesture)
    }
    
    private func setupTapGestureRecognizer() {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
        tapGesture.cancelsTouchesInView = false
        view.addGestureRecognizer(tapGesture)
    }
    
    // MARK: - Notification Handlers
    
    @objc private func appDidBecomeActive() {
        // refreshLibraryLayout()
    }
    
    @objc private func libraryDidChange(_ notification: Notification) {
        // Fetch the updated library items
        if !Thread.isMainThread {
            DispatchQueue.main.async { [weak self] in
                self?.libraryDidChange(notification)
            }
            return
        }
        
        libraryItems = LibraryManager.shared.fetchLibraryItems()
        
        // Update filteredLibraryItems based on current search text
        if let searchText = librarySearchBar.text, !searchText.isEmpty {
            filterLibraryItems(with: searchText)
        } else {
            filteredLibraryItems = libraryItems
        }
        
        // Refresh the library layout
        refreshLibraryLayout()
    }

    
    private func setupLogoutObserver() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(handleLogout),
                                               name: .userDidLogout,
                                               object: nil)
    }


    @objc private func handleLogout() {
        // First, reset all states
        isAuthLayoutVisible = false
        isArtistLayoutVisible = false
        isLibraryVisible = false  // Add this line to reset library visibility
        
        // Stop any playing audio
        stopAudio()
        
        // Hide layouts
        authLayout.hide()
        artistLayout.hide()
        libraryView.isHidden = true  // Add this line to hide library view
        
        // Reset user button to person icon
        userIconButton.setImage(UIImage(systemName: "person"), for: .normal)
        libraryButton.setImage(UIImage(systemName: "books.vertical"), for: .normal)  // Reset library button icon
        
        // Clear and refresh the library view
        if let containerView = libraryView.subviews.first {
            containerView.subviews.forEach { $0.removeFromSuperview() }
        }
        
        // Show current content
        showCurrentContentViews()
        
        // Explicitly show main UI elements AFTER showCurrentContentViews
        libraryButton.isHidden = false
        searchIconButton.isHidden = false
        musicCollectionView.isHidden = false
        libraryButton.alpha = 1
        searchIconButton.alpha = 1
        
        // Force refresh the library to ensure it's empty
        LibraryManager.shared.forceRefresh()
        
        // Update the UI with empty state
        updateLibraryVisibility()
    }

    // MARK: - AutoPlay
    
    @objc private func autoPlayButtonTapped() {
        isAutoPlayEnabled.toggle()
        let buttonTitle = isAutoPlayEnabled ? "Disable Auto-Play" : "Enable Auto-Play"
        autoPlayButton.setTitle(buttonTitle, for: .normal)
        
        if let layout = musicCollectionView.collectionViewLayout as? CarouselLayout {
            layout.isAutoPlayEnabled = isAutoPlayEnabled
        }
        
        audioPlayer.enableBackgroundPlayback(isEnabled: isAutoPlayEnabled)

        if isAutoPlayEnabled && currentPlayingIndex != nil && !audioPlayer.isPlaying {
            audioPlayer.play()
        }
        // Show/hide navigation buttons with animation
        if isAutoPlayEnabled {
            previousButton.isHidden = false
            nextButton.isHidden = false

            UIView.animate(withDuration: 0.15) {
                self.previousButton.alpha = 1
                self.nextButton.alpha = 1
            }
        } else {
            UIView.animate(withDuration: 0.15) {
                self.previousButton.alpha = 0
                self.nextButton.alpha = 0
            } completion: { _ in
                self.previousButton.isHidden = true
                self.nextButton.isHidden = true
            }
        }
    }
    
    func moveToNextSnippet(from index: Int) {
        var nextIndex = index + 1
        if nextIndex >= filteredMusicSnippets.count {
            nextIndex = 0 // Loop back to the beginning
        }
        if nextIndex == index {
            // Only one snippet, avoid infinite loop
            return
        }
        let nextSnippet = filteredMusicSnippets[nextIndex]
        if nextSnippet.type == .music, let url = nextSnippet.url {
            self.scrollToItem(at: nextIndex, animated: true)
            // No need to call playAudioFromUrl here, scrollToItem will handle it
        } else {
            // Skip non-music snippets
            moveToNextSnippet(from: nextIndex)
        }
    }
    
    private func hasPreviousSnippet(from currentIndex: Int) -> Bool {
        guard currentIndex > 0 else { return false }
        // Check all previous indices for a music snippet
        for index in (0..<currentIndex).reversed() {
            if filteredMusicSnippets[index].type == .music {
                return true
            }
        }
        return false
    }

    private func hasNextSnippet(from currentIndex: Int) -> Bool {
        guard currentIndex < filteredMusicSnippets.count - 1 else { return false }
        // Check all next indices for a music snippet
        for index in (currentIndex + 1)..<filteredMusicSnippets.count {
            if filteredMusicSnippets[index].type == .music {
                return true
            }
        }
        return false
    }

    private func updateNavigationButtonStates() {
        guard let currentIndex = currentPlayingIndex else {
            previousButton.isEnabled = false
            nextButton.isEnabled = false
            return
        }
        
        previousButton.isEnabled = hasPreviousSnippet(from: currentIndex)
        nextButton.isEnabled = hasNextSnippet(from: currentIndex)
        
        // Update alpha for visual feedback
        previousButton.alpha = previousButton.isEnabled ? 1.0 : 0.3
        nextButton.alpha = nextButton.isEnabled ? 1.0 : 0.3
    }

    private func disableAutoPlay() {
        if isAutoPlayEnabled {
            isAutoPlayEnabled = false
            autoPlayButton.setTitle("Enable Auto-Play", for: .normal)
            
            if let layout = musicCollectionView.collectionViewLayout as? CarouselLayout {
                layout.isAutoPlayEnabled = false
            }
            
            audioPlayer.enableBackgroundPlayback(isEnabled: false)
            
            // Hide navigation buttons with animation
            UIView.animate(withDuration: 0.15) {
                self.previousButton.alpha = 0
                self.nextButton.alpha = 0
            } completion: { _ in
                self.previousButton.isHidden = true
                self.nextButton.isHidden = true
            }
        }
    }
    
    // MARK: - Button Actions
    
    @objc private func libraryButtonTapped() {
        handleViewSwitch(to: isLibraryVisible ? .main : .library)
    }
    
    @objc private func searchIconTapped() {
        searchBar.isHidden = false
        searchIconButton.isHidden = true
        libraryButton.isHidden = true
        
        searchBar.alpha = 0
        
        UIView.animate(withDuration: 0.15) {
            self.searchBar.alpha = 1
            self.musicCollectionView.alpha = 1
        } completion: { _ in
            self.searchBar.becomeFirstResponder()
            self.checkCollectionViewVisibility()
        }
    }
    
    @objc private func importButtonTapped() {
        if let text = searchBar.text, !text.isEmpty {
            performSearch(with: text)
        }
        view.endEditing(true)
    }
    
    @objc private func previousButtonTapped() {
        guard let currentIndex = currentPlayingIndex,
              hasPreviousSnippet(from: currentIndex) else { return }
              
        // Find the previous music snippet
        for index in (0..<currentIndex).reversed() {
            if filteredMusicSnippets[index].type == .music {
                scrollToItem(at: index, animated: true)
                break
            }
        }
    }
    
    @objc private func nextButtonTapped() {
        guard let currentIndex = currentPlayingIndex else { return }
        moveToNextSnippet(from: currentIndex)
    }
    
    @objc private func userIconButtonTapped() {
        if AuthManager.shared.isUserLoggedIn() {
            toggleArtistLayout()
        } else {
            toggleAuthLayout()
        }
    }

    private func hideUserIconButton() {
        UIView.animate(withDuration: 0.15) {
            self.userIconButton.alpha = 0
        } completion: { _ in
            self.userIconButton.isHidden = true
        }
    }

    private func showUserIconButton() {
        self.userIconButton.isHidden = false
        UIView.animate(withDuration: 0.15) {
            self.userIconButton.alpha = 1
        }
    }
    
    // MARK: - Gesture Handlers
    
    @objc private func handleSwipeGesture(_ gesture: UISwipeGestureRecognizer) {
        if gesture.state == .ended {
            view.endEditing(true)
        }
    }
    
    @objc private func handleTapGesture(_ gesture: UITapGestureRecognizer) {
        // Dismiss any active keyboard
        view.endEditing(true)
        
        let location = gesture.location(in: view)
        
        // Handle hiding of search bars if necessary
        if !librarySearchBar.isHidden && !librarySearchBar.frame.contains(location) {
            hideLibrarySearchBar()
        }
        
        if !searchBar.isHidden && !searchBar.frame.contains(location) {
            hideSearchBar()
        }
        
        if musicCollectionView.frame.contains(location) {
            print("Tap inside collection view")
        } else {
            print("Tap outside collection view")
        }
    }
    
    // MARK: - Infinite Scroll
    
    private func handleNewSnippets() {
        isLoadingMore = false

        // Preserve current playing snippet info
        let currentlyPlayingSnippetID = currentPlayingIndex != nil ? filteredMusicSnippets[currentPlayingIndex!].id : nil
        let wasPlaying = audioPlayer.isPlaying
        let oldCount = filteredMusicSnippets.count
        
        // Update data source
        filteredMusicSnippets = snippetBuffer.getSnippets()
        let newCount = filteredMusicSnippets.count
        
        // If counts are different, we must do a full reload
        if oldCount != newCount {
            currentPlayingIndex = currentlyPlayingSnippetID.flatMap { playingID in
                filteredMusicSnippets.firstIndex { $0.id == playingID }
            }
            musicCollectionView.reloadData()
        } else {
            // Counts are same, safe to do partial update
            if let playingID = currentlyPlayingSnippetID,
               let newIndex = filteredMusicSnippets.firstIndex(where: { $0.id == playingID }) {
                currentPlayingIndex = newIndex
                musicCollectionView.reloadData()
            } else {
                currentPlayingIndex = nil
                audioPlayer.stopAudio()
                musicCollectionView.reloadData()
            }
        }
        
        // Restore playback if it was playing
        if wasPlaying, let index = currentPlayingIndex {
            // Don't restart the audio if it's the same snippet still playing
            updateCellPlayingState(at: index, isPlaying: true)
        }
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if isInSearchMode {
            if !isLoadingSearchResults && hasMoreSearchResults {
                let visibleIndexPaths = musicCollectionView.indexPathsForVisibleItems
                let visibleIndices = visibleIndexPaths.map { $0.item }
                guard let maxVisibleIndex = visibleIndices.max(),
                      maxVisibleIndex < filteredMusicSnippets.count else {
                    return
                }
                if (filteredMusicSnippets.count - maxVisibleIndex) <= 5 {
                    // Fetch next page
                    fetchNextSearchResults()
                }
            }
        } else {
            // Existing code to fetch more random snippets
            guard !isInSearchMode else { return }
            
            let visibleIndexPaths = musicCollectionView.indexPathsForVisibleItems
            let visibleIndices = visibleIndexPaths.map { $0.item }
            
            // Only proceed if we have visible indices and they're within bounds
            guard let maxVisibleIndex = visibleIndices.max(),
                  maxVisibleIndex < filteredMusicSnippets.count else {
                return
            }
            
            if snippetBuffer.shouldFetchMoreSnippets(currentIndex: maxVisibleIndex) && !isLoadingMore {
                isLoadingMore = true
                
                // Only reload the section to add the loading indicator
                let section = IndexSet(integer: 0)
                musicCollectionView.reloadSections(section)
                
                snippetBuffer.fetchMoreSnippets()
            }
        }
    }
    
    // MARK: - UI Helpers
    
    private func hideSearchBar() {
        searchBar.resignFirstResponder()
        
        UIView.animate(withDuration: 0.05) {
            self.searchBar.alpha = 0
            self.musicCollectionView.alpha = 1
        } completion: { _ in
            self.searchBar.isHidden = true
            self.searchIconButton.isHidden = false
            self.libraryButton.isHidden = false
            self.searchIconButton.alpha = 0
            self.libraryButton.alpha = 0
            
            UIView.animate(withDuration: 0.05) {
                self.searchIconButton.alpha = 1
                self.libraryButton.alpha = 1
            }
            
            self.checkCollectionViewVisibility()
        }
    }
    
//    private func updatePlaceholderVisibility() {
//        searchBar.setPlaceholderVisibility(searchBar.text.isEmpty)
//    }
    
    private func resetAllAnimations() {
        for cell in musicCollectionView.visibleCells {
            if let musicCell = cell as? MusicSnippetCell {
                musicCell.animationView.fullReset()
            }
        }
    }
    
    func checkCollectionViewVisibility() {
        print("Collection view is in hierarchy: \(musicCollectionView.superview != nil)")
        print("Collection view is hidden: \(musicCollectionView.isHidden)")
        print("Collection view frame: \(musicCollectionView.frame)")
        print("Collection view bounds: \(musicCollectionView.bounds)")
        print("Number of items: \(musicCollectionView.numberOfItems(inSection: 0))")
    }
    
    func scrollToItem(at index: Int, animated: Bool) {
        guard index < filteredMusicSnippets.count else { return }
        let indexPath = IndexPath(item: index, section: 0)
        musicCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: animated)
        
        // Add this to trigger playback when scrolling programmatically
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
            let snippet = self?.filteredMusicSnippets[index]
            if snippet?.type == .music, let url = snippet?.url {
                self?.playAudioFromUrl(url, at: index)
            }
        }
    }
    
    private func resetItemViewAppearance(_ itemView: UIView) {
        itemView.backgroundColor = .black
        itemView.subviews.compactMap { $0 as? UILabel }.forEach { label in
            if label.text == itemView.subviews.compactMap({ $0 as? UILabel }).first?.text {
                label.textColor = .white // Title label
            } else {
                label.textColor = .lightGray // Artist label
            }
        }
    }
    
    private func hideCurrentContentViews() {
        
        libraryView.isHidden = true
        musicCollectionView.isHidden = true
        artistLayout.isHidden = true
        leaderboardButton.isHidden = true
        autoPlayButton.isHidden = true  // Add this line
        
        
        // Add these lines
        if isAutoPlayEnabled {
            UIView.animate(withDuration: 0.15) {
                self.previousButton.alpha = 0
                self.nextButton.alpha = 0
            } completion: { _ in
                self.previousButton.isHidden = true
                self.nextButton.isHidden = true
            }
        }
        
        disableAutoPlay()  // Add this line

    }


    private func showCurrentContentViews() {
        libraryView.isHidden = !isLibraryVisible
        musicCollectionView.isHidden = isLibraryVisible
        artistLayout.isHidden = !isArtistLayoutVisible
        libraryButton.isHidden = false
        searchIconButton.isHidden = false
        leaderboardButton.isHidden = false
        autoPlayButton.isHidden = isLibraryVisible  // Add this line

        
        // Add these lines
        if isAutoPlayEnabled && !isLibraryVisible && !isArtistLayoutVisible {
            previousButton.isHidden = false
            nextButton.isHidden = false
            UIView.animate(withDuration: 0.15) {
                self.previousButton.alpha = 1
                self.nextButton.alpha = 1
            }
        }
    }
    
    private func handleViewSwitch(to view: ViewType) {
        // Stop any currently playing audio before switching views
        stopAudio()
        
        switch view {
        case .library:
            isLibraryVisible = true
            updateLibraryVisibility()
        case .main:
            isLibraryVisible = false
            updateLibraryVisibility()
        }
    }

    // MARK: Auth Stuff
    
    private func handleAuthSuccess() {
        // Add a check to ensure we have a valid artist ID
        guard let artistId = AuthManager.shared.getArtistId() else {
            print("Error: No artist ID available after login")
            return
        }
        LibraryManager.shared.forceRefresh()
        
        toggleAuthLayout()
        
        // Give a small delay to ensure all states are updated
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
            self?.toggleArtistLayout()
        }
    }
    
    private func prefetchUserGlobeTexture() {
        guard let artistId = AuthManager.shared.getArtistId() else { return }
        
        ArtistManager.shared.fetchArtistData(artistId: artistId) { result in
            switch result {
            case .success(let artistData):
                if let globeTextureState = artistData.globeTexture,
                   let image = UIImage(data: globeTextureState.textureData) {
                    // Cache the globe texture
                    ArtistManager.globeTextureCache[artistId] = image
                }
            case .failure(let error):
                print("Error prefetching user globe texture: \(error)")
            }
        }
    }
    
    private func prefetchArtistData() {
        guard let artistId = AuthManager.shared.getArtistId() else { return }
        
        // Start loading indicator if needed
        artistDataQueue.async { [weak self] in
            ArtistManager.shared.fetchArtistData(artistId: artistId, forceRefresh: true) { [weak self] result in
                switch result {
                case .success(let artistData):
                    self?.cachedArtistData = artistData
                    
                    // Pre-fetch and cache the globe texture
                    if let globeTextureState = artistData.globeTexture,
                       let image = UIImage(data: globeTextureState.textureData) {
                        ArtistManager.globeTextureCache[artistId] = image
                    }
                    
                    // Also pre-fetch songs
                    self?.prefetchArtistSongs(artistId: artistId)
                    
                case .failure(let error):
                    print("Failed to pre-fetch artist data: \(error)")
                }
            }
        }
    }
    
    private func prefetchArtistSongs(artistId: String) {
        ArtistManager.shared.fetchArtistSongs(for: artistId) { result in
            switch result {
            case .success(let response):
                print("Successfully pre-fetched \(response.songs.count) songs")
            case .failure(let error):
                print("Failed to pre-fetch artist songs: \(error)")
            }
        }
    }
    
    @objc private func handleAppActive() {
        if AuthManager.shared.isUserLoggedIn() {
            prefetchArtistData()
        }
    }
    
    private func toggleAuthLayout() {
        isAuthLayoutVisible.toggle()

        if isAuthLayoutVisible {
            stopAudio()
            hideCurrentContentViews()
            authLayout.show()
            
            UIView.animate(withDuration: 0.15, animations: {
                self.libraryButton.alpha = 0
                self.searchIconButton.alpha = 0
                self.userIconButton.alpha = 0
            }) { _ in
                self.libraryButton.isHidden = true
                self.searchIconButton.isHidden = true
                self.userIconButton.isHidden = true
            }
        } else {
            authLayout.hide()
            showCurrentContentViews()
            
            self.libraryButton.isHidden = false
            self.searchIconButton.isHidden = false
            self.userIconButton.isHidden = false
            UIView.animate(withDuration: 0.15, animations: {
                self.libraryButton.alpha = 1
                self.searchIconButton.alpha = 1
                self.userIconButton.alpha = 1
            })
        }
    }
    
    // MARK: Leaderboard Stuff
    
    @objc private func leaderboardButtonTapped() {
        toggleLeaderboardLayout()
    }
    
    private func toggleLeaderboardLayout() {
        isLeaderboardVisible.toggle()

        if isLeaderboardVisible {
            stopAudio()
            hideCurrentContentViews()
            leaderboardLayout.show()
            
            UIView.animate(withDuration: 0.15, animations: {
                self.libraryButton.alpha = 0
                self.searchIconButton.alpha = 0
                self.userIconButton.alpha = 0
            }) { _ in
                self.libraryButton.isHidden = true
                self.searchIconButton.isHidden = true
                self.userIconButton.isHidden = true
            }
        } else {
            stopAudio()
            leaderboardLayout.hide()
            showCurrentContentViews()
            
            self.libraryButton.isHidden = false
            self.searchIconButton.isHidden = false
            self.userIconButton.isHidden = false
            UIView.animate(withDuration: 0.15) {
                self.libraryButton.alpha = 1
                self.searchIconButton.alpha = 1
                self.userIconButton.alpha = 1
            }
        }
    }
    
    // MARK: Artist Stuff
    
    @objc private func dismissArtistLayout() {
        toggleArtistLayout()
    }

    private func toggleArtistLayout() {
        guard AuthManager.shared.isUserLoggedIn() else {
            toggleAuthLayout()
            return
        }
        
        isArtistLayoutVisible.toggle()
        
        if isArtistLayoutVisible {
            hideCurrentContentViews()
            
            // Use cached data if available
            if let artistId = AuthManager.shared.getArtistId() {
                artistLayout.configure(
                    artistId: artistId,
                    presentationMode: .owned
                )
                
                // If we have cached data, apply it immediately
                if let cachedData = cachedArtistData {
                    DispatchQueue.main.async {
                        self.artistLayout.show()
                        // The layout will use the cached data from ArtistManager
                    }
                } else {
                    // If no cached data, show layout normally
                    artistLayout.show()
                }
            }
            
            view.bringSubviewToFront(userIconButton)
            view.bringSubviewToFront(libraryButton)
            view.bringSubviewToFront(searchIconButton)
            
            stopAudio()
            userIconButton.setImage(UIImage(systemName: "square.fill"), for: .normal)
            
            UIView.animate(withDuration: 0.15) {
                self.libraryButton.alpha = 0
                self.searchIconButton.alpha = 0
            } completion: { _ in
                self.libraryButton.isHidden = true
                self.searchIconButton.isHidden = true
            }
        } else {
            UIView.animate(withDuration: 0.15) {
                self.artistLayout.alpha = 0
            } completion: { _ in
                self.artistLayout.hide()
                self.showCurrentContentViews()
                self.userIconButton.setImage(UIImage(systemName: "person"), for: .normal)
                
                self.libraryButton.isHidden = false
                self.searchIconButton.isHidden = false
                UIView.animate(withDuration: 0.15) {
                    self.libraryButton.alpha = 1
                    self.searchIconButton.alpha = 1
                }
            }
        }
    }

    // MARK: - Library Visibility
    
    private func updateLibraryVisibility() {
        if isLibraryVisible {
            // Reset search states
            librarySearchBar.resignFirstResponder()
            librarySearchBar.isHidden = true
            librarySearchBar.alpha = 0
            librarySearchButton.isHidden = false
            
            // Setup library view initial state
            libraryView.isHidden = false
            libraryView.alpha = 0
            
            // Fetch items based on current auth state
            libraryItems = LibraryManager.shared.fetchLibraryItems()
            filteredLibraryItems = libraryItems
            
            // Prepare layout
            libraryLayout?.resetItemVisibility()
            
            // Apply layout and animate
            libraryLayout?.apply(to: libraryView, with: filteredLibraryItems) { [weak self] in
                guard let self = self else { return }
                
                // Hide main content
                self.hideMainContent { [weak self] in
                    guard let self = self else { return }
                    
                    // Show library content
                    UIView.animate(withDuration: 0.15) {
                        self.libraryView.alpha = 1
                        self.librarySearchButton.alpha = 1
                    }
                }
            }
        } else {
            // Hide library content
            UIView.animate(withDuration: 0.15) {
                self.librarySearchButton.alpha = 0
                self.librarySearchBar.alpha = 0
                self.libraryView.alpha = 0
            } completion: { [weak self] _ in
                guard let self = self else { return }
                
                self.librarySearchButton.isHidden = true
                self.librarySearchBar.isHidden = true
                self.libraryView.isHidden = true
                
                // Show main content
                self.showMainContent()
            }
        }
        
        updateLoadingIndicatorVisibility()
        updateLibraryButtonIcon()
    }
    // Helper methods
    private func hideMainContent(completion: @escaping () -> Void) {
        UIView.animate(withDuration: 0.15) {
            self.musicCollectionView.alpha = 0
            self.searchIconButton.alpha = 0
            self.leaderboardButton.alpha = 0
            self.userIconButton.alpha = 0
            self.autoPlayButton.alpha = 0  // Add this line
        } completion: { _ in
            self.musicCollectionView.isHidden = true
            self.searchIconButton.isHidden = true
            self.leaderboardButton.isHidden = true
            self.userIconButton.isHidden = true
            self.autoPlayButton.isHidden = true  // Add this line
            completion()
        }
    }

    private func showMainContent() {
        self.musicCollectionView.isHidden = false
        self.searchIconButton.isHidden = false
        self.leaderboardButton.isHidden = false
        self.userIconButton.isHidden = false
        self.autoPlayButton.isHidden = false  // Add this line
        
        UIView.animate(withDuration: 0.15) {
            self.musicCollectionView.alpha = 1
            self.searchIconButton.alpha = 1
            self.leaderboardButton.alpha = 1
            self.userIconButton.alpha = 1
            self.autoPlayButton.alpha = 1  // Add this line
        }
    }


    
    private func updateLibraryButtonIcon() {
        let iconName = isLibraryVisible ? "square.fill" : "books.vertical"
        libraryButton.setImage(UIImage(systemName: iconName), for: .normal)
    }
    
    private func updateLoadingIndicatorVisibility() {
        if isLibraryVisible {
            loadingIndicator.stopAnimating()
        } else {
            if isLoadingSnippets {
                loadingIndicator.startAnimating()
            } else {
                loadingIndicator.stopAnimating()
            }
        }
    }
    
    private func hideLibrarySearchBar() {
        librarySearchBar.resignFirstResponder()
        
        UIView.animate(withDuration: 0.15) {
            self.librarySearchBar.alpha = 0
        } completion: { _ in
            self.librarySearchBar.isHidden = true
            self.librarySearchButton.isHidden = false
            
            UIView.animate(withDuration: 0.15) {
                self.librarySearchButton.alpha = 1
            }
            
            self.libraryButton.isHidden = false
        }
    }
    
    private func filterLibraryItems(with searchText: String) {
        if searchText.isEmpty {
            filteredLibraryItems = libraryItems
        } else {
            filteredLibraryItems = libraryItems.filter { item in
                let titleMatch = item.title?.range(of: searchText, options: .caseInsensitive) != nil
                let artistMatch = item.artist?.range(of: searchText, options: .caseInsensitive) != nil
                return titleMatch || artistMatch
            }
        }
        
        libraryLayout?.apply(to: libraryView, with: filteredLibraryItems) {
            // Completion code if needed
        }
    }
    
    // MARK: - Data Loading
    
    func fetchRandomMusicSnippets() {
        // If ArtistLayout is visible, do not fetch or play snippets
        if isArtistLayoutVisible {
            print("ArtistLayout is visible. Skipping fetching random music snippets.")
            return
        }
        
        isInSearchMode = false
        autoPlayButton.isHidden = false

        
        stopAudio()
        isLoadingSnippets = true
        updateLoadingIndicatorVisibility()
        musicCollectionView.isHidden = true
        
        snippetManager.fetchRandomMusicSnippets { [weak self] result in
            guard let self = self else { return }
            
            DispatchQueue.main.async {
                self.isLoadingSnippets = false
                self.updateLoadingIndicatorVisibility()
                self.musicCollectionView.isHidden = false
                switch result {
                case .success(let snippets):
                    self.musicSnippets = snippets
                    self.filteredMusicSnippets = self.filterSnippets(snippets)
                    self.resetAllAnimations()
                    self.musicCollectionView.reloadData()
                    
                    if let firstMusicIndex = self.filteredMusicSnippets.firstIndex(where: { $0.type == .music }) {
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                            // Check again if ArtistLayout is visible before playing
                            if !self.isArtistLayoutVisible {
                                self.scrollToItem(at: firstMusicIndex, animated: true)
                            } else {
                                print("ArtistLayout became visible. Skipping snippet playback.")
                            }
                        }
                    }
                    
                    print("DEBUG: Random music snippets fetched and collection view reloaded")
                    
                case .failure(let error):
                    if case APIError.unauthorized = error {
                        self.handleUnauthorizedError()
                    } else {
                        print("Error fetching recommendations: \(error)")
                        // Handle other errors
                    }
                }
            }
        }
    }

    
    func performSearch(with query: String) {
        isInSearchMode = true
        currentSearchPage = 1
        isLoadingSearchResults = false
        hasMoreSearchResults = true
        currentSearchQuery = query
        
        stopAudio()
        isLoadingSnippets = true
        updateLoadingIndicatorVisibility()
        musicCollectionView.isHidden = true
        
        snippetManager.fetchRecommendations(for: query, page: currentSearchPage) { [weak self] result in
            guard let self = self else { return }
            
            DispatchQueue.main.async {
                self.isLoadingSnippets = false
                self.updateLoadingIndicatorVisibility()
                self.musicCollectionView.isHidden = false
                
                switch result {
                case .success(let snippets):
                    self.musicSnippets = snippets
                    self.filteredMusicSnippets = self.filterSnippets(snippets)
                    self.resetAllAnimations()
                    self.musicCollectionView.reloadData()
                    
                    if self.filteredMusicSnippets.count >= self.maxSearchResults || snippets.count < self.resultsPerPage {
                        self.hasMoreSearchResults = false
                    }
                    
                    self.hideSearchBar()
                    
                    if let firstMusicIndex = self.filteredMusicSnippets.firstIndex(where: { $0.type == .music }) {
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                            self.scrollToItem(at: firstMusicIndex, animated: true)
                        }
                    }
                    
                case .failure(let error):
                    if case APIError.unauthorized = error {
                        self.handleUnauthorizedError()
                    } else {
                        print("Error fetching recommendations: \(error)")
                        // Handle other errors
                    }
                }
            }
        }
    }
    
    private func fetchNextSearchResults() {
        guard let query = currentSearchQuery,
              !isLoadingSearchResults,
              hasMoreSearchResults else {
            return
        }
        isLoadingSearchResults = true
        currentSearchPage += 1
        snippetManager.fetchRecommendations(for: query, page: currentSearchPage) { [weak self] result in
            guard let self = self else { return }
            DispatchQueue.main.async {
                self.isLoadingSearchResults = false
                switch result {
                case .success(let snippets):
                    if snippets.isEmpty {
                        self.hasMoreSearchResults = false
                    } else {
                        self.musicSnippets.append(contentsOf: snippets)
                        self.filteredMusicSnippets = self.filterSnippets(self.musicSnippets)
                        self.musicCollectionView.reloadData()
                        
                        if self.filteredMusicSnippets.count >= self.maxSearchResults || snippets.count < self.resultsPerPage {
                            self.hasMoreSearchResults = false
                        }
                    }
                case .failure(let error):
                    print("Error fetching more search results: \(error)")
                    self.hasMoreSearchResults = false
                }
            }
        }
    }
    
    private func filterSnippets(_ snippets: [MusicSnippet]) -> [MusicSnippet] {
        var filtered = snippets.filter { $0.type != .search } // Always remove search button
        
        // Include the .reload snippet only in search mode
        if isInSearchMode && currentSearchPage == 1 {
            // Ensure only one .reload snippet is present
            if let reloadSnippet = snippets.first(where: { $0.type == .reload }) {
                filtered.removeAll { $0.type == .reload }
                filtered.insert(reloadSnippet, at: 0) // Insert at the beginning
            }
        } else {
            filtered = filtered.filter { $0.type != .reload }
        }
        
        return filtered
    }
    
    private func handleUnauthorizedError() {
        print("Unauthorized access. Please check your API key.")
        // In a real app, consider showing an alert or triggering re-authentication
    }
    
    private func filterSearchButton(from snippets: [MusicSnippet]) -> [MusicSnippet] {
        return snippets.filter { $0.type != .search }
    }
    
    func clearAllCaches() {
        // Clear ArtistManager cache
        if let artistId = AuthManager.shared.getArtistId() {
            ArtistManager.shared.clearCache(for: artistId)
        }
        
        // Clear any API response caches
        URLCache.shared.removeAllCachedResponses()
        
        // Refresh data
        fetchRandomMusicSnippets()
    }
    
    // MARK: - Audio Playback
    
    func playAudioFromUrl(_ urlString: String, at index: Int) {
        // Check if ArtistLayout is visible
        if isArtistLayoutVisible {
            print("ArtistLayout is visible. Skipping snippet playback.")
            return
        }
        
        
        
        // Stop any existing audio and reset state
        stopAudio()
        currentlyPlayingItem = nil  // Reset library item reference
        
        print("DEBUG: Attempting to play audio from URL: \(urlString)")
        
        if index == currentPlayingIndex {
            togglePlayPause()
            return
        }
        
        if let oldIndex = currentPlayingIndex {
            updateCellPlayingState(at: oldIndex, isPlaying: false)
        }
        
        let snippet = filteredMusicSnippets[index]
        
        audioPlayer.onAudioPositionUpdate = { [weak self] currentTime, duration in
            DispatchQueue.main.async {
                if let cell = self?.musicCollectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? MusicSnippetCell {
                    cell.animationView.updateAnimationProgress(currentTime)
                }
            }
        }
        
        audioPlayer.onPlaybackStarted = { [weak self] in
            DispatchQueue.main.async {
                self?.updateCellPlayingState(at: index, isPlaying: true)
            }
        }
        
        audioPlayer.onPlaybackPaused = { [weak self] in
            DispatchQueue.main.async {
                self?.updateCellPlayingState(at: index, isPlaying: false)
            }
        }
        
        audioPlayer.onPlaybackResumed = { [weak self] in
            DispatchQueue.main.async {
                self?.updateCellPlayingState(at: index, isPlaying: true)
            }
        }
        
        audioPlayer.playAudioFromUrl(urlString,
                                    title: snippet.title,
                                    artist: snippet.artist) { [weak self] success in
            DispatchQueue.main.async {
                if success {
                    self?.currentPlayingIndex = index
                    self?.updateNavigationButtonStates()
                } else {
                    print("DEBUG: Failed to play audio for index: \(index)")
                }
            }
        }
        
        audioPlayer.onPlaybackEnded = { [weak self] in
            DispatchQueue.main.async {
                guard let self = self else { return }
                if self.isAutoPlayEnabled {
                    // When autoplay is enabled, move to next snippet
                    self.moveToNextSnippet(from: index)
                } else {
                    // When autoplay is disabled, replay the current snippet
                    if let url = self.filteredMusicSnippets[index].url {
                        self.playAudioFromUrl(url, at: index)
                    }
                }
            }
        }
    }

    
    private func playAudioFromUrl(_ urlString: String, item: LibraryItem, itemView: UIView) {
        audioPlayer.onPlaybackStarted = { [weak self] in
            print("Playback started")
            DispatchQueue.main.async {
                self?.isAudioPaused = false
                self?.updatePlayingState(for: item, isPlaying: true)
            }
        }
        
        audioPlayer.onPlaybackPaused = { [weak self] in
            print("Playback paused")
            DispatchQueue.main.async {
                self?.isAudioPaused = true
                self?.updatePlayingState(for: item, isPlaying: false)
            }
        }
        
        audioPlayer.onPlaybackResumed = { [weak self] in
            print("Playback resumed")
            DispatchQueue.main.async {
                self?.isAudioPaused = false
                self?.updatePlayingState(for: item, isPlaying: true)
            }
        }
        
        audioPlayer.playAudioFromUrl(urlString) { [weak self] success in
            DispatchQueue.main.async {
                if success {
                    print("Now playing: \(item.title ?? "Unknown") by \(item.artist ?? "Unknown")")
                    self?.currentlyPlayingItem = item
                    self?.isAudioPaused = false
                } else {
                    print("Failed to play audio")
                    self?.updatePlayingState(for: item, isPlaying: false)
                    self?.isAudioPaused = true
                }
            }
        }
    }
    
    private func updateCellPlayingState(at index: Int, isPlaying: Bool) {
        if let cell = musicCollectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? MusicSnippetCell {
            let snippet = filteredMusicSnippets[index]
            cell.configure(with: snippet, isPlaying: isPlaying)
            cell.setNumber(index)
        }
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        checkCollectionViewVisibility()
        let center = view.convert(musicCollectionView.center, to: musicCollectionView)
        if let indexPath = musicCollectionView.indexPathForItem(at: center) {
            print("Center item index: \(indexPath.item)")
            
            // Add bounds check before accessing the array
            guard indexPath.item < filteredMusicSnippets.count else {
                print("Index out of range: \(indexPath.item) >= \(filteredMusicSnippets.count)")
                return
            }
            
            let snippet = filteredMusicSnippets[indexPath.item]
            if snippet.type == .music {
                if indexPath.item != currentPlayingIndex {
                    if let url = snippet.url {
                        // Check if ArtistLayout is visible before playing
                        if !isArtistLayoutVisible {
                            playAudioFromUrl(url, at: indexPath.item)
                        } else {
                            print("ArtistLayout is visible. Skipping snippet playback.")
                        }
                    }
                }
            }
        } else {
            print("No item at center")
        }
    }
    
    private func stopAudio() {
        audioPlayer.stopAudio()
        
        // Reset main view state
        if let currentIndex = currentPlayingIndex {
            updateCellPlayingState(at: currentIndex, isPlaying: false)
        }
        currentPlayingIndex = nil
        
        // Reset library state
        if let currentItem = currentlyPlayingItem {
            updatePlayingState(for: currentItem, isPlaying: false)
        }
        currentlyPlayingItem = nil
        
        // Reset all library items' playing states
        LibraryManager.shared.resetAllPlayingStates()
    }
    
    func togglePlayPause() {
        audioPlayer.togglePlayPause()
    }
    
    // MARK: - Library Management
    
    private func handleLibraryItemTap(item: LibraryItem, itemView: UIView) {
        print("tapped")
        if item == currentlyPlayingItem {
            if audioPlayer.isPlaying {
                audioPlayer.togglePlayPause()
                updatePlayingState(for: item, isPlaying: false)
            } else {
                audioPlayer.togglePlayPause()
                updatePlayingState(for: item, isPlaying: true)
            }
        } else {
            stopAudio()
            playLibraryItem(item, itemView: itemView)
        }
    }
    
    private func handleLibraryItemRename(item: LibraryItem, newTitle: String) {
        print("DEBUG: Entering handleLibraryItemRename method")
        print("DEBUG: Attempting to rename item in Core Data")
        
        // Update the item's title
        item.title = newTitle
        
        // Save the changes to Core Data
        do {
            try CoreDataStack.shared.saveLibraryContext()
            print("DEBUG: Successfully renamed item to: \(newTitle) in Core Data")
            
            // Update the libraryItems array
            if let index = libraryItems.firstIndex(where: { $0.objectID == item.objectID }) {
                libraryItems[index] = item
                print("DEBUG: Updated libraryItems array with new title")
            } else {
                print("DEBUG: Could not find item in libraryItems array")
            }
            
            // Update the filteredLibraryItems based on current search text
            if let searchText = librarySearchBar.text, !searchText.isEmpty {
                filterLibraryItems(with: searchText)
            } else {
                filteredLibraryItems = libraryItems
            }
            
            // Refresh the library view with the updated filteredLibraryItems
            DispatchQueue.main.async { [weak self] in
                self?.libraryLayout?.apply(to: self?.libraryView ?? UIView(), with: self?.filteredLibraryItems ?? []) {
                    print("DEBUG: Library view refreshed after rename")
                }
            }
            
        } catch {
            print("ERROR: Failed to save renamed item: \(error)")
            
            // Optionally, you can revert the title change or notify the user
            // For example:
            // item.title = oldTitle
            // Show an alert to the user about the failure
            let alert = UIAlertController(title: "Rename Failed",
                                          message: "Unable to rename the item. Please try again.",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)
        }
    }
    
    private func playLibraryItem(_ item: LibraryItem, itemView: UIView) {
        guard let gsutilUri = item.gsutilUri else {
            print("Error: No gsutilUri found for library item")
            return
        }
        
        fetchAuthenticatedURL(for: gsutilUri) { [weak self] authenticatedURL in
            guard let self = self, let url = authenticatedURL else { return }
            DispatchQueue.main.async {
                // Reset all other items' playing states
                self.libraryItems.forEach { $0.isPlaying = false }
                
                // Set the current item as playing
                item.isPlaying = true
                self.updatePlayingState(for: item, isPlaying: true)
                
                self.audioPlayer.playAudioFromUrl(url) { success in
                    if success {
                        print("Now playing: \(item.title ?? "Unknown") by \(item.artist ?? "Unknown")")
                        self.currentlyPlayingItem = item
                    } else {
                        print("Failed to play audio")
                        self.updatePlayingState(for: item, isPlaying: false)
                    }
                }
            }
        }
    }
    
    func addToLibrary(snippet: MusicSnippet) {
        let song = ModelConverters.convertToSong(from: snippet)
        LibraryManager.shared.addToLibrary(song) { result in
            switch result {
            case .success:
                print("Successfully added to library")
                // Handle success (e.g., update UI, show confirmation)
            case .failure(let error):
                print("Failed to add to library: \(error)")
                // Handle error (e.g., show error alert)
            }
        }
        updateLibraryStatus(for: snippet, isInLibrary: true)
        print("Successfully added to library")

        // Fetch the updated library items
        libraryItems = LibraryManager.shared.fetchLibraryItems()

        // Update filteredLibraryItems based on current search text
        if let searchText = librarySearchBar.text, !searchText.isEmpty {
            filterLibraryItems(with: searchText)
        } else {
            filteredLibraryItems = libraryItems
        }

        // Refresh the library layout
        DispatchQueue.main.async { [weak self] in
            self?.libraryLayout?.apply(to: self?.libraryView ?? UIView(), with: self?.filteredLibraryItems ?? []) {
                print("Library layout refreshed after adding new item")
            }
        }
    }

    
    private func removeFromLibrary(item: LibraryItem) {
        guard let gsutilUri = item.gsutilUri else {
            print("Error: No gsutilUri found for library item")
            return
        }
        
        // Step 1: Optimistically remove the item from the local data source and UI
        if let index = libraryItems.firstIndex(where: { $0.objectID == item.objectID }) {
            libraryItems.remove(at: index)
            print("DEBUG: Removed item from libraryItems array")
            
            // Update the filteredLibraryItems based on current search text
            if let searchText = librarySearchBar.text, !searchText.isEmpty {
                filterLibraryItems(with: searchText)
            } else {
                filteredLibraryItems = libraryItems
            }
            
            // Refresh the library view with the updated filteredLibraryItems
            DispatchQueue.main.async { [weak self] in
                self?.libraryLayout?.apply(to: self?.libraryView ?? UIView(), with: self?.filteredLibraryItems ?? []) {
                    print("DEBUG: Library view refreshed after item removal")
                }
            }
        } else {
            print("DEBUG: Could not find item in libraryItems array to remove")
        }
        
        // Step 2: Save the changes to Core Data
        do {
            try CoreDataStack.shared.saveLibraryContext()
            print("DEBUG: Successfully removed item from Core Data")
        } catch {
            print("ERROR: Failed to remove item from Core Data: \(error)")
            // Since Core Data failed, you might want to revert the UI changes
            // Optionally, re-add the item to libraryItems and refresh the UI
            libraryItems.append(item)
            if let searchText = librarySearchBar.text, !searchText.isEmpty {
                filterLibraryItems(with: searchText)
            } else {
                filteredLibraryItems = libraryItems
            }
            DispatchQueue.main.async { [weak self] in
                self?.libraryLayout?.apply(to: self?.libraryView ?? UIView(), with: self?.filteredLibraryItems ?? []) {
                    print("DEBUG: Library view refreshed after Core Data failure")
                }
                // Show an error alert to the user
                self?.showErrorAlert("Unable to remove the item from your library. Please try again.")
            }
            return // Exit early since Core Data failed
        }
        
        // Step 3: Perform the removal operation in the background
        LibraryManager.shared.removeFromLibrary(gsutilUri: gsutilUri) { [weak self] result in
            switch result {
            case .success:
                print("Successfully removed from library on server")
                // Optionally, you can perform additional actions on success
            case .failure(let error):
                print("Failed to remove from library on server: \(error)")
                
                // Step 4: Handle the error by reverting UI changes and informing the user
                DispatchQueue.main.async {
                    // Re-add the item to the local data source
                    self?.libraryItems.append(item)
                    if let searchText = self?.librarySearchBar.text, !searchText.isEmpty {
                        self?.filterLibraryItems(with: searchText)
                    } else {
                        self?.filteredLibraryItems = self?.libraryItems ?? []
                    }
                    
                    // Refresh the library view with the reverted data
                    self?.libraryLayout?.apply(to: self?.libraryView ?? UIView(), with: self?.filteredLibraryItems ?? []) {
                        print("DEBUG: Library view refreshed after server removal failure")
                    }
                    
                    // Show an error alert to the user
                    self?.showErrorAlert("Failed to remove the item from your library. Please try again.")
                }
            }
        }
    }

    // Helper method to present an error alert
    private func showErrorAlert(_ message: String) {
        let alert = UIAlertController(title: "Removal Failed",
                                      message: message,
                                      preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }
    
    private func updateLibraryStatus(for snippet: MusicSnippet, isInLibrary: Bool) {
        if let index = musicSnippets.firstIndex(where: { $0.id == snippet.id }) {
            musicSnippets[index].isInLibrary = isInLibrary
        }
        if let filteredIndex = filteredMusicSnippets.firstIndex(where: { $0.id == snippet.id }) {
            filteredMusicSnippets[filteredIndex].isInLibrary = isInLibrary
            musicCollectionView.reloadItems(at: [IndexPath(item: filteredIndex, section: 0)])
        }
    }
    
    private func updatePlayingState(for item: LibraryItem, isPlaying: Bool) {
        LibraryManager.shared.updatePlayingState(for: item, isPlaying: isPlaying)
        libraryLayout?.updateItemState(for: item, isPlaying: isPlaying)
    }
    
    private func refreshLibraryLayout() {
        guard isLibraryVisible else { return }
        libraryLayout?.apply(to: libraryView, with: filteredLibraryItems) {
            print("Library layout refreshed in MainViewController")
        }
    }
    
    @objc private func librarySearchButtonTapped() {
        self.libraryButton.isHidden = true
        
        librarySearchButton.isHidden = true
        librarySearchBar.isHidden = false
        
        UIView.animate(withDuration: 0.15) {
            self.librarySearchBar.alpha = 1
        } completion: { _ in
            self.librarySearchBar.becomeFirstResponder()
        }
    }
    
    // MARK: - API Interaction
    
    private func fetchAuthenticatedURL(for gsutilUri: String, completion: @escaping (String?) -> Void) {
        let endpoint = "/api/authenticate-uri/"
        let parameters = ["gsutil_uri": gsutilUri]
        
        guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters) else {
            print("Failed to serialize JSON parameters.")
            completion(nil)
            return
        }
        
        APIClient.shared.perform(endpoint, method: "POST", body: jsonData) { (result: Result<[String: String], APIError>) in
            switch result {
            case .success(let response):
                if let authenticatedURL = response["authenticated_url"] {
                    completion(authenticatedURL)
                } else {
                    print("Failed to parse authenticated URL from response")
                    completion(nil)
                }
            case .failure(let error):
                print("Error fetching authenticated URL: \(error)")
                completion(nil)
            }
        }
    }
    
    // MARK: - Keyboard Handling
    
    @objc private func keyboardWillShow(notification: NSNotification) {
        guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
        
        let keyboardHeight = keyboardFrame.height
        let collectionViewBottomInset = view.frame.height - musicCollectionView.frame.maxY
        let inset = max(0, keyboardHeight - collectionViewBottomInset)
        
        UIView.animate(withDuration: 0.15) {
            self.musicCollectionView.contentInset.bottom = inset
            self.musicCollectionView.verticalScrollIndicatorInsets.bottom = inset
            self.musicCollectionView.layoutIfNeeded()
        }
    }
    
    @objc private func keyboardWillHide(notification: NSNotification) {
        UIView.animate(withDuration: 0.15) {
            self.musicCollectionView.contentInset.bottom = 0
            self.musicCollectionView.verticalScrollIndicatorInsets.bottom = 0
            self.musicCollectionView.layoutIfNeeded()
        }
    }
}

// MARK: - UICollectionViewDataSource

extension MainViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        let additionalCell = (isLoadingMore || isLoadingSearchResults) ? 1 : 0
        return filteredMusicSnippets.count + additionalCell
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        if (isLoadingMore || isLoadingSearchResults) && indexPath.item == filteredMusicSnippets.count {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LoadingIndicatorCell", for: indexPath) as! LoadingIndicatorCell
            cell.startAnimating()
            return cell
        }
        
        if isLoadingMore && indexPath.item == filteredMusicSnippets.count {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LoadingIndicatorCell", for: indexPath) as! LoadingIndicatorCell
            cell.startAnimating()
            return cell
        }
        
        // Guard against index out of bounds
        guard indexPath.item < filteredMusicSnippets.count else {
            return UICollectionViewCell()
        }
        
        if indexPath.item == filteredMusicSnippets.count {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LoadingIndicatorCell", for: indexPath) as! LoadingIndicatorCell
            cell.startAnimating()
            return cell
        }
        
        let snippet = filteredMusicSnippets[indexPath.item]

        switch snippet.type {
        case .music:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MusicSnippetCell", for: indexPath) as? MusicSnippetCell else {
                return UICollectionViewCell()
            }
            cell.animationView.fullReset()
            let isPlaying = indexPath.item == currentPlayingIndex
            cell.configure(with: snippet, isPlaying: isPlaying)
            cell.setNumber(indexPath.item)
            
            cell.onCellTapped = { [weak self] in
                if let url = snippet.url {
                    self?.playAudioFromUrl(url, at: indexPath.item)
                }
            }
            
            cell.onAddToLibraryTapped = { [weak self] in
                if snippet.isInLibrary {
                    // self?.removeFromLibrary(snippet: snippet)
                } else {
                    self?.addToLibrary(snippet: snippet)
                }
            }
            
            // Add the new handler for viewing artist
            cell.onViewArtistTapped = { [weak self] artistId in
                // Stop any current playback
                self?.stopAudio()
                
                // Configure and show the artist layout
                self?.artistLayout.configure(
                    artistId: artistId,
                    presentationMode: .viewOnly,
                    onBack: { [weak self] in
                        // Hide artist layout and show main content
                        self?.isArtistLayoutVisible = false
                        self?.artistLayout.hide()
                        self?.showCurrentContentViews()
                        
                        // Show UI elements
                        self?.libraryButton.isHidden = false
                        self?.searchIconButton.isHidden = false
                        UIView.animate(withDuration: 0.15) {
                            self?.libraryButton.alpha = 1
                            self?.searchIconButton.alpha = 1
                        }
                    }
                )
                
                // Hide main content and show artist layout
                self?.hideCurrentContentViews()
                self?.isArtistLayoutVisible = true
                self?.artistLayout.show()
                
                // Update UI elements
                UIView.animate(withDuration: 0.15) {
                    self?.libraryButton.alpha = 0
                    self?.searchIconButton.alpha = 0
                } completion: { _ in
                    self?.libraryButton.isHidden = true
                    self?.searchIconButton.isHidden = true
                }
            }
            
            cell.onViewFullSongTapped = { [weak self] snippet in
                guard let self = self else { return }
                
                let streamingLinksLayout = StreamingLinksLayout(frame: self.view.bounds)
                streamingLinksLayout.delegate = self
                streamingLinksLayout.configure(with: snippet)  // Now we can pass snippet directly
                self.view.addSubview(streamingLinksLayout)
                streamingLinksLayout.show()
            }
            
            return cell
            
        case .search, .reload:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SpecialCardCell", for: indexPath) as? SpecialCardCell else {
                return UICollectionViewCell()
            }
            cell.configure(with: snippet)
            return cell
        }
    }
}

// MARK: - UICollectionViewDelegate

extension MainViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print("DEBUG: Cell selected at index: \(indexPath.item)")
        
        guard indexPath.item < filteredMusicSnippets.count else {
            return
        }
        
        let snippet = filteredMusicSnippets[indexPath.item]
        
        switch snippet.type {
        case .music:
            if indexPath.item == currentPlayingIndex {
                audioPlayer.togglePlayPause()
                updateCellPlayingState(at: indexPath.item, isPlaying: audioPlayer.isPlaying)
            } else {
                if let url = snippet.url {
                    playAudioFromUrl(url, at: indexPath.item)
                } else {
                    print("DEBUG: No URL available for this snippet")
                }
            }
        case .search:
            print("DEBUG: Search action triggered")
        case .reload:
            print("DEBUG: Reload action triggered")
            fetchRandomMusicSnippets()
        }
    }
}

// MARK: - UICollectionViewDelegateFlowLayout

extension MainViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        let screenWidth = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
        let referenceWidth: CGFloat = 428 // Width of iPhone 15 screen in portrait
        let referenceItemWidth: CGFloat = 200
        let referenceItemHeight: CGFloat = 160
        
        let widthScale = screenWidth / referenceWidth
        let itemWidth = referenceItemWidth * widthScale
        let itemHeight = referenceItemHeight * widthScale
        
        return CGSize(width: itemWidth, height: itemHeight)
    }
}

// MARK: - UITextViewDelegate

extension MainViewController: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        if textView == searchBar {
            // Existing search bar logic
            // updatePlaceholderVisibility()
            importButton.isHidden = textView.text.isEmpty
        } else if textView == librarySearchBar {
            // Library search bar logic
            // librarySearchBar.setPlaceholderVisibility(textView.text.isEmpty)
            filterLibraryItems(with: textView.text)
        }
    }
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if text == "\n" {
            if textView == searchBar {
                performSearch(with: textView.text)
                hideSearchBar()
            } else if textView == librarySearchBar {
                textView.resignFirstResponder() // Dismiss keyboard
            }
            return false
        }
        return true
    }
}

extension MainViewController: StreamingLinksLayoutDelegate {
    func streamingLinksLayout(_ layout: StreamingLinksLayout, didSelectURL url: URL) {
        // Handle streaming link selection
        let safariVC = SFSafariViewController(url: url)
        safariVC.modalPresentationStyle = .pageSheet
        present(safariVC, animated: true)
    }
}


// MARK: - AuthLayout Delegate

extension MainViewController: AuthLayoutDelegate {
    func authLayout(_ authLayout: AuthLayout, didShowError message: String) {
        // Present an alert to inform the user about the error
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }
}

extension MainViewController: LeaderboardLayoutDelegate {
    func leaderboardLayout(_ layout: LeaderboardLayout, didTapClose: Void) {
        layout.hide()
        
        // Update the state flag
        isLeaderboardVisible = false
        
        // Show all main content buttons again
        musicCollectionView.isHidden = false
        libraryButton.isHidden = false
        searchIconButton.isHidden = false
        userIconButton.isHidden = false
        leaderboardButton.isHidden = false
        autoPlayButton.isHidden = false  // Add this line to show autoplay button
        
        UIView.animate(withDuration: 0.15) {
            self.musicCollectionView.alpha = 1
            self.libraryButton.alpha = 1
            self.searchIconButton.alpha = 1
            self.userIconButton.alpha = 1
            self.leaderboardButton.alpha = 1
            self.autoPlayButton.alpha = 1  // Add this line to animate autoplay button
        }
    }
    
    func leaderboardLayout(_ layout: LeaderboardLayout, didRequestProfileView artistId: String) {
        let artistLayout = ArtistLayout(frame: view.bounds)
        view.addSubview(artistLayout)
        
        artistLayout.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            artistLayout.topAnchor.constraint(equalTo: view.topAnchor),
            artistLayout.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            artistLayout.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            artistLayout.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        // Configure with view-only mode and back handler
        artistLayout.configure(
            artistId: artistId,
            presentationMode: .viewOnly,
            onBack: { [weak self, weak layout] in
                // Hide artist layout and show leaderboard again
                artistLayout.removeFromSuperview()
                
                // Make sure leaderboard is visible
                layout?.isHidden = false
                UIView.animate(withDuration: 0.15) {
                    layout?.alpha = 1
                }
            }
        )
        
        // Hide leaderboard without removing it
        layout.alpha = 0
        layout.isHidden = true
        
        // Show the artist layout
        artistLayout.show()
    }
}

// MARK: - SpecialCardCell

class SpecialCardCell: UICollectionViewCell {
    
    // MARK: - UI Components
    
    private let actionLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
        label.textColor = .white
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    // MARK: - Initializers
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Setup Methods
    
    private func setupViews() {
        contentView.backgroundColor = UIColor(red: 5/255, green: 5/255, blue: 5/255, alpha: 1)
        contentView.layer.cornerRadius = 4
        contentView.clipsToBounds = true
        
        contentView.addSubview(actionLabel)
        
        NSLayoutConstraint.activate([
            actionLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            actionLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
    }
    
    // MARK: - Configuration
    
    func configure(with snippet: MusicSnippet) {
        switch snippet.type {
        case .search:
            actionLabel.text = "Search"
        case .reload:
            actionLabel.text = "Random"
        case .music:
            actionLabel.text = "Play"
        }
    }
}

// MARK: - MusicSnippetCell

class MusicSnippetCell: UICollectionViewCell {
    
    // MARK: - Callbacks
    
    var onSelectButtonTapped: (() -> Void)?
    var onAddToLibraryTapped: (() -> Void)?
    var onCellTapped: (() -> Void)?
    var onViewArtistTapped: ((String) -> Void)?
    var onViewFullSongTapped: ((MusicSnippet) -> Void)?

    private var currentSnippet: MusicSnippet?
    
    // MARK: - UI Components
    
    private var isSearchMode: Bool = false
    private var isInLibrary: Bool = false
    
    private let numberLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 14, weight: .medium)
        label.textColor = .white
        label.backgroundColor = UIColor(white: 0.3, alpha: 0.7)
        label.textAlignment = .center
        label.layer.cornerRadius = 10
        label.clipsToBounds = true
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    let animationView: AnimationView = {
        let view = AnimationView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .clear
        return view
    }()
    
    let optionsButton: UIButton = {
        let button = UIButton(type: .system)
        if let image = UIImage(systemName: "ellipsis")?.rotated(by: .pi / 2) {
            button.setImage(image, for: .normal)
        } else {
            button.setImage(UIImage(systemName: "ellipsis"), for: .normal)
        }
        button.tintColor = UIColor(white: 0.3, alpha: 0.9)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    let addToLibraryButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Save", for: .normal)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
        button.setTitleColor(UIColor.black.withAlphaComponent(0.9), for: .normal)
        button.backgroundColor = .clear
        button.layer.cornerRadius = 8
        button.layer.borderWidth = 1
        button.layer.borderColor = UIColor.black.withAlphaComponent(0.9).cgColor
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setContentHuggingPriority(.required, for: .vertical)
        button.setContentCompressionResistancePriority(.required, for: .vertical)
        return button
    }()
    
    // MARK: - Initializers
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupViews()
    }
    
    // MARK: - Setup Methods
    
    private func setupViews() {
        let backgroundImage = UIImage(named: "IMG_20231217_0007")
        contentView.layer.contents = backgroundImage?.cgImage
        contentView.layer.contentsGravity = .resizeAspectFill
        contentView.layer.cornerRadius = 4
        contentView.clipsToBounds = true
        
        // contentView.addSubview(numberLabel)
        contentView.addSubview(addToLibraryButton)
        contentView.addSubview(optionsButton)
        contentView.addSubview(animationView)
        
        NSLayoutConstraint.activate([
            addToLibraryButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
            addToLibraryButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            addToLibraryButton.widthAnchor.constraint(equalToConstant: 100),
            addToLibraryButton.heightAnchor.constraint(equalToConstant: 34),
            
//            numberLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
//            numberLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
//            numberLabel.widthAnchor.constraint(equalToConstant: 20),
//            numberLabel.heightAnchor.constraint(equalToConstant: 20),
            
            optionsButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
            optionsButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -4),
            optionsButton.widthAnchor.constraint(equalToConstant: 20),
            optionsButton.heightAnchor.constraint(equalToConstant: 20),
            
            animationView.topAnchor.constraint(equalTo: contentView.topAnchor),
            animationView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            animationView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            animationView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
        
        addToLibraryButton.addTarget(self, action: #selector(addToLibraryButtonTapped), for: .touchUpInside)
        
        // Configure the options button's menu
        optionsButton.showsMenuAsPrimaryAction = true
        optionsButton.menu = createContextMenu()
    }
    
    // MARK: - Configuration
    
    func configure(with musicSnippet: MusicSnippet, isPlaying: Bool) {
        self.isInLibrary = musicSnippet.isInLibrary
        self.currentSnippet = musicSnippet // Store the current snippet
        
        updateLibraryButtonVisibility()
        addToLibraryButton.removeTarget(nil, action: nil, for: .allEvents)
        addToLibraryButton.addTarget(self, action: #selector(addToLibraryButtonTapped), for: .touchUpInside)
        
        animationView.updateWithSnippet(musicSnippet)
        numberLabel.text = ""
        
        // Update the context menu
        optionsButton.menu = createContextMenu()
    }
    
    private func createContextMenu() -> UIMenu {
        var menuItems: [UIAction] = []

        // View Artist action
        let viewArtistAction = UIAction(
            title: "View Artist",
            image: UIImage(systemName: "person")
        ) { [weak self] _ in
            guard let self = self,
                  let snippet = self.currentSnippet,
                  let artistId = snippet.artistId else {
                return
            }
            self.onViewArtistTapped?(artistId)
        }
        menuItems.append(viewArtistAction)
        
        // View Full Song action (only show if streaming links exist)
        if let snippet = currentSnippet,
           let streamingLinks = snippet.streamingLinks,
           !streamingLinks.isEmpty {
            let viewFullSongAction = UIAction(
                title: "View Full Song",
                image: UIImage(systemName: "music.note")
            ) { [weak self] _ in
                guard let self = self,
                      let snippet = self.currentSnippet else { return }
                self.onViewFullSongTapped?(snippet)
            }
            menuItems.append(viewFullSongAction)
        }
        
        return UIMenu(title: "", children: menuItems)
    }
    
    func updateLibraryButtonState(isInLibrary: Bool) {
        if isInLibrary {
            // Configure button for "Remove" state if needed
        } else {
            addToLibraryButton.setTitle("Save", for: .normal)
            addToLibraryButton.backgroundColor = .clear
        }
    }
    
    private func updateLibraryButtonVisibility() {
        addToLibraryButton.isHidden = isInLibrary
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        addToLibraryButton.isHidden = false
        addToLibraryButton.removeTarget(nil, action: nil, for: .allEvents)
    }
    
    func setNumber(_ number: Int) {
        numberLabel.text = "\(number + 1)"
    }
    
    // MARK: - Actions
    
    @objc private func addToLibraryButtonTapped() {
        isInLibrary = true
        updateLibraryButtonVisibility()
        onAddToLibraryTapped?()
    }
    
    @objc private func cellTapped() {
        onCellTapped?()
    }
    
    // MARK: - Additional Methods
    
    func showAddToLibraryButton() {
        isInLibrary = false
        updateLibraryButtonVisibility()
    }
}

// MARK: - NSLayoutConstraint Extension

extension NSLayoutConstraint {
    func withPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
        self.priority = priority
        return self
    }
}

private enum ViewType {
    case main
    case library
}

extension MainViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        if searchBar == self.searchBar {
            if let text = searchBar.text, !text.isEmpty {
                performSearch(with: text)
            }
            searchBar.resignFirstResponder()
        } else if searchBar == librarySearchBar {
            searchBar.resignFirstResponder()
        }
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchBar == librarySearchBar {
            filterLibraryItems(with: searchText)
        }
    }
    
    // Add this method to handle the cancel button
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        if searchBar == self.searchBar {
            hideSearchBar()
        } else if searchBar == librarySearchBar {
            hideLibrarySearchBar()
        }
    }
}

class LoadingIndicatorCell: UICollectionViewCell {
    private let activityIndicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView(style: .medium)
        indicator.color = .white
        indicator.translatesAutoresizingMaskIntoConstraints = false
        indicator.hidesWhenStopped = true
        return indicator
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupViews() {
        contentView.backgroundColor = .clear
        contentView.addSubview(activityIndicator)
        
        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        activityIndicator.stopAnimating()
    }
    
    func startAnimating() {
        activityIndicator.startAnimating()
    }
}



import Foundation

class MusicSnippetManager {
    var musicSnippets: [MusicSnippet] = []
    let baseURL = APIClient.shared.baseURL

    public let maxSearchResults = 100
    public let resultsPerPage = 10
    
    private func createMusicSnippet(from song: Song) -> MusicSnippet {
        return MusicSnippet(
            type: .music,
            gsutilUri: song.gsutilUri,
            url: song.authenticatedUrl,
            artist: song.artist,
            artistId: song.artistId,
            title: song.title,
            amplitudes: song.amplitudes,
            spectralCentroids: song.spectralCentroids,
            onsetTimes: song.onsetTimes,
            duration: song.duration,
            streamingLinks: song.streamingLinks,
            odesliUrl: song.odesliUrl,
            odesliTitle: song.odesliTitle,
            odesliArtist: song.odesliArtist
        )
    }
    
    func fetchRandomMusicSnippets(completion: @escaping (Result<[MusicSnippet], Error>) -> Void) {
        let endpoint = "/events/"
        let parameters: [String: Any] = ["event_type": "random_generation"]
        
        guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters) else {
            completion(.failure(APIError.invalidResponse))
            return
        }
        
        APIClient.shared.perform(endpoint, method: "POST", body: jsonData) { (result: Result<UserMusicEvent, APIError>) in
            switch result {
            case .success(let event):
                let snippets = event.songs.map { self.createMusicSnippet(from: $0) }
                completion(.success(snippets)) // No special buttons added
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
        
    func fetchRecommendations(for text: String, page: Int = 1, completion: @escaping (Result<[MusicSnippet], Error>) -> Void) {
        print("DEBUG: Fetching recommendations for page \(page)")
        let endpoint = "/events/"
        let parameters: [String: Any] = [
            "event_type": "recommendation",
            "input_text": text,
            "page": page,
            "per_page": resultsPerPage
        ]
        
        guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters) else {
            completion(.failure(APIError.invalidResponse))
            return
        }
        
        APIClient.shared.perform(endpoint, method: "POST", body: jsonData) { (result: Result<UserMusicEvent, APIError>) in
            switch result {
            case .success(let event):
                let snippets = event.songs.map { self.createMusicSnippet(from: $0) }
                let snippetsWithButtons = page == 1 ? self.addSpecialButtons(to: snippets) : snippets
                print("DEBUG: Fetched \(snippets.count) snippets for page \(page)")
                completion(.success(snippetsWithButtons))
            case .failure(let error):
                print("DEBUG: Error fetching page \(page): \(error)")
                completion(.failure(error))
            }
        }
    }
    
    func downloadAudioData(for snippet: MusicSnippet, completion: @escaping (Result<Data, Error>) -> Void) {
        guard let urlString = snippet.url, let url = URL(string: urlString) else {
            completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil)))
            return
        }

        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data else {
                completion(.failure(NSError(domain: "No data", code: 0, userInfo: nil)))
                return
            }
            
            completion(.success(data))
        }.resume()
    }
    
    public func addSpecialButtons(to snippets: [MusicSnippet]) -> [MusicSnippet] {
        var allSnippets = [MusicSnippet(type: .search)]
        allSnippets.append(contentsOf: snippets)
        allSnippets.append(MusicSnippet(type: .reload))
        return allSnippets
    }
}

// SnippetBuffer.swift

import Foundation

class SnippetBuffer {
    
    // MARK: - Properties
    
    private var snippets: [MusicSnippet] = []
    private var isFetching = false
    private let snippetManager = MusicSnippetManager()
    private let bufferThreshold = 5  // Number of items left before fetching more
    private let maxBufferSize = 100  // Optional maximum buffer size to prevent unlimited growth
    
    // Callback to notify when new snippets are available
    var onNewSnippetsAvailable: (() -> Void)?
    // var onLoadingStateChanged: ((Bool) -> Void)?
    var onError: ((Error) -> Void)?


    // MARK: - Initialization
    
    init() {
        // Initial fetch of snippets
        fetchMoreSnippets()
    }
    
    // MARK: - Public Methods
    
    func getSnippets() -> [MusicSnippet] {
        return snippets
    }
    
    func getSnippet(at index: Int) -> MusicSnippet? {
        guard index >= 0 && index < snippets.count else { return nil }
        return snippets[index]
    }
    
    func totalSnippetsCount() -> Int {
        return snippets.count
    }
    
    func shouldFetchMoreSnippets(currentIndex: Int) -> Bool {
        return !isFetching && (totalSnippetsCount() - currentIndex) <= bufferThreshold
    }
    
    func fetchMoreSnippets() {
        guard !isFetching else { return }
        isFetching = true
        // onLoadingStateChanged?(true)

        snippetManager.fetchRandomMusicSnippets { [weak self] result in
            guard let self = self else { return }
            self.isFetching = false
            // onLoadingStateChanged?(false)

            switch result {
            case .success(let newSnippets):
                // Remove duplicates
                let uniqueSnippets = newSnippets.filter { newSnippet in
                    !self.snippets.contains(where: { $0.id == newSnippet.id })
                }
                
                self.snippets.append(contentsOf: uniqueSnippets)
                
                // Optionally limit buffer size
                if self.snippets.count > self.maxBufferSize {
                    self.snippets.removeFirst(self.snippets.count - self.maxBufferSize)
                }
                
                DispatchQueue.main.async {
                    self.onNewSnippetsAvailable?()
                }
            case .failure(let error):
                print("Error fetching more snippets: \(error)")
                self.onError?(error)

                // Handle error as needed
            }
        }
    }
}

//
//  UserMusicEvent.swift
//  iOSdeejMessage
//
//  Created by DJ Bray on 6/25/24.
//

import Foundation

struct UserMusicEvent: Codable {
    let id: Int
    let eventType: String
    let songs: [Song]
    let inputText: String
    let timestamp: Date
    let recommendationType: String?
    let interactionDuration: Int?
    
    enum CodingKeys: String, CodingKey {
        case id, songs, timestamp
        case eventType = "event_type"
        case inputText = "input_text"
        case recommendationType = "recommendation_type"
        case interactionDuration = "interaction_duration"
    }
}