import Foundation
import SwiftData
import SwiftUI

/// ViewModel for managing playlists and building mixes.
@MainActor
@Observable
final class PlaylistViewModel {
    var selectedPlaylist: Playlist?
    var showExportSheet = false
    var exportError: String?

    /// Temporary status message shown as a toast.
    var statusMessage: String?

    /// Three mix target slots — configurable in Settings.
    var mixTargets: [Playlist?] = [nil, nil, nil]

    /// Legacy single target (uses slot 0).
    var targetPlaylist: Playlist? {
        get { mixTargets[0] }
        set { setMixTarget(0, playlist: newValue) }
    }

    /// Set a mix target at the given slot (0, 1, 2).
    func setMixTarget(_ slot: Int, playlist: Playlist?) {
        guard slot >= 0 && slot < 3 else { return }
        mixTargets[slot] = playlist
        if let id = playlist?.id {
            UserDefaults.standard.set(id.uuidString, forKey: "mixTarget\(slot)ID")
        } else {
            UserDefaults.standard.removeObject(forKey: "mixTarget\(slot)ID")
        }
    }

    /// Get the display name for a mix slot.
    func mixTargetName(_ slot: Int) -> String {
        guard slot >= 0 && slot < 3 else { return "Mix \(slot + 1)" }
        return mixTargets[slot]?.name ?? "Mix \(slot + 1)"
    }

    func restoreTargetPlaylist(from playlists: [Playlist]) {
        for slot in 0..<3 {
            guard mixTargets[slot] == nil,
                  let savedID = UserDefaults.standard.string(forKey: "mixTarget\(slot)ID"),
                  let uuid = UUID(uuidString: savedID),
                  let playlist = playlists.first(where: { $0.id == uuid }) else { continue }
            mixTargets[slot] = playlist
        }
    }

    var defaultCrossfadeDuration: TimeInterval = 2.0

    func showStatus(_ message: String, duration: TimeInterval = 4.0) {
        statusMessage = message
        Task { @MainActor in
            try? await Task.sleep(for: .seconds(duration))
            if self.statusMessage == message {
                self.statusMessage = nil
            }
        }
    }

    // MARK: - Quick Add

    /// Add a track to a specific mix slot (0, 1, 2).
    func quickAddToMix(slot: Int, track: Track, context: ModelContext) -> Bool {
        guard slot >= 0 && slot < 3 else { return false }
        guard let target = mixTargets[slot] else {
            showStatus("Mix \(slot + 1) not set — go to Settings → Mix Targets")
            return false
        }

        if isDuplicate(track: track, in: target, context: context) {
            showStatus("Already in \(target.name)")
            return false
        }

        addTrack(track, to: target, context: context)
        showStatus("→ \(target.name)")
        return true
    }

    func quickAddToTarget(track: Track, context: ModelContext) -> Bool {
        return quickAddToMix(slot: 0, track: track, context: context)
    }

    /// Safe duplicate check using a fetch query instead of traversing relationships.
    func isDuplicate(track: Track, in playlist: Playlist, context: ModelContext? = nil) -> Bool {
        // If we have a context, use a safe fetch-based check
        if let context {
            let trackID = track.id
            let playlistID = playlist.id
            let descriptor = FetchDescriptor<PlaylistEntry>(
                predicate: #Predicate<PlaylistEntry> { entry in
                    entry.playlist?.id == playlistID && entry.track?.id == trackID
                }
            )
            let count = (try? context.fetchCount(descriptor)) ?? 0
            return count > 0
        }
        // Fallback: try relationship traversal but catch crashes
        let trackID = track.id
        return playlist.entries.contains { entry in
            guard let t = entry.track else { return false }
            return t.id == trackID
        }
    }

    // MARK: - Playlist CRUD

    func createPlaylist(name: String, context: ModelContext) -> Playlist {
        let playlist = Playlist(name: name)
        context.insert(playlist)
        try? context.save()
        return playlist
    }

    func deletePlaylist(_ playlist: Playlist, context: ModelContext) {
        if selectedPlaylist?.id == playlist.id {
            selectedPlaylist = nil
        }
        context.delete(playlist)
        try? context.save()
    }

    func renamePlaylist(_ playlist: Playlist, to name: String, context: ModelContext) {
        playlist.name = name
        playlist.dateModified = Date()
        try? context.save()
    }

    // MARK: - Track Management

    func addTrack(_ track: Track, to playlist: Playlist, context: ModelContext, warnDuplicate: Bool = true) {
        if warnDuplicate && isDuplicate(track: track, in: playlist, context: context) {
            showStatus("⚠ Already in \(playlist.name)")
            return
        }
        playlist.addTrack(track, crossfadeDuration: defaultCrossfadeDuration)
        try? context.save()
    }

    func addTracks(_ tracks: [Track], to playlist: Playlist, context: ModelContext) {
        // Sort by full file path to preserve folder structure ordering
        // Use numeric-aware sorting so "12.1.1" comes before "12.1.2"
        let sorted = tracks.sorted { 
            $0.filePath.compare($1.filePath, options: [.numeric, .caseInsensitive]) == .orderedAscending
        }
        // Log first 10 paths to verify ordering
        for (i, t) in sorted.prefix(10).enumerated() {
            print("addTracks sorted[\(i)]: \(t.filePath)")
        }
        var added = 0
        var skipped = 0
        for track in sorted {
            if isDuplicate(track: track, in: playlist, context: context) {
                skipped += 1
            } else {
                playlist.addTrack(track, crossfadeDuration: defaultCrossfadeDuration)
                added += 1
            }
        }
        try? context.save()
        if skipped > 0 {
            showStatus("Added \(added) tracks, \(skipped) duplicates skipped")
        }
    }

    func removeEntry(_ entry: PlaylistEntry, from playlist: Playlist, context: ModelContext) {
        playlist.removeEntry(at: entry.position)
        try? context.save()
    }

    func moveEntry(in playlist: Playlist, from source: Int, to destination: Int, context: ModelContext) {
        playlist.moveEntry(from: source, to: destination)
        try? context.save()
    }

    func updateCrossfade(for entry: PlaylistEntry, duration: TimeInterval, context: ModelContext) {
        entry.crossfadeDuration = duration
        try? context.save()
    }

    func updateGain(for entry: PlaylistEntry, gain: Double, context: ModelContext) {
        entry.gainAdjustment = gain
        try? context.save()
    }

    // MARK: - Cue Points

    func addCuePoint(to track: Track, at timestamp: TimeInterval, type: CuePointType = .marker, name: String = "", context: ModelContext) {
        let cuePoint = CuePoint(name: name, timestamp: timestamp, type: type)
        track.cuePoints.append(cuePoint)
        try? context.save()
    }

    func removeCuePoint(_ cuePoint: CuePoint, from track: Track, context: ModelContext) {
        track.cuePoints.removeAll { $0.id == cuePoint.id }
        context.delete(cuePoint)
        try? context.save()
    }

    // MARK: - Import Files to Playlist

    func importFilesToPlaylist(
        urls: [URL],
        playlist: Playlist,
        libraryManager: LibraryManager,
        context: ModelContext
    ) async {
        for url in urls where MetadataService.isSupportedAudioFile(url) {
            let accessing = url.startAccessingSecurityScopedResource()
            defer { if accessing { url.stopAccessingSecurityScopedResource() } }

            do {
                try await libraryManager.importFile(url, context: context)
            } catch {
                print("PlaylistViewModel: import failed for \(url.lastPathComponent): \(error)")
            }

            let fileName = url.lastPathComponent
            let descriptor = FetchDescriptor<Track>(predicate: #Predicate { $0.fileName == fileName })
            if let track = try? context.fetch(descriptor).first {
                playlist.addTrack(track, crossfadeDuration: defaultCrossfadeDuration)
            }
        }
        try? context.save()
    }

    /// Calculate the total effective mix duration including crossfades.
    func mixDuration(for playlist: Playlist) -> TimeInterval {
        let entries = playlist.sortedEntries
        var total: TimeInterval = 0
        for (index, entry) in entries.enumerated() {
            total += entry.effectiveDuration
            if index > 0 {
                total -= entry.crossfadeDuration
            }
        }
        return max(0, total)
    }
}
