import Foundation

// MARK: - Acapella Bundle

/// Represents an .acapella bundle (directory) containing a song file and recorded audio parts.
/// Structure:
///   SongTitle.acapella/
///     song.txt              - The song definition file
///     soprano.wav           - Recorded audio for Soprano part (optional)
///     alto.wav              - Recorded audio for Alto part (optional)
///     tenor.wav             - Recorded audio for Tenor part (optional)
///     bass.wav              - Recorded audio for Bass part (optional)
///     ... etc.
///
struct AcapellaBundle {
    let url: URL
    let song: Song
    var recordedParts: [String: URL]   // part name -> WAV file URL

    /// The song.txt file URL within the bundle
    var songFileURL: URL {
        return url.appendingPathComponent("song.txt")
    }

    /// WAV file URL for a specific part
    func audioURL(forPart partName: String) -> URL {
        return url.appendingPathComponent("\(partName.lowercased()).wav")
    }

    /// Whether a specific part has a recording
    func hasRecording(forPart partName: String) -> Bool {
        return recordedParts[partName] != nil
    }

    /// Part names that have recordings
    var recordedPartNames: [String] {
        return Array(recordedParts.keys).sorted()
    }
}

// MARK: - Bundle Manager

struct BundleManager {

    /// Load an .acapella bundle from a directory URL
    static func load(from url: URL) throws -> AcapellaBundle {
        let fm = FileManager.default

        // Check the directory exists
        var isDir: ObjCBool = false
        guard fm.fileExists(atPath: url.path, isDirectory: &isDir), isDir.boolValue else {
            throw BundleError.notADirectory(url.path)
        }

        // Parse song.txt
        let songURL = url.appendingPathComponent("song.txt")
        guard fm.fileExists(atPath: songURL.path) else {
            throw BundleError.missingSongFile(url.path)
        }

        let song = try SongFileParser.parse(url: songURL)

        // Find recorded WAV files
        var recordedParts: [String: URL] = [:]
        for part in song.parts {
            let wavURL = url.appendingPathComponent("\(part.name.lowercased()).wav")
            if fm.fileExists(atPath: wavURL.path) {
                recordedParts[part.name] = wavURL
            }
        }

        return AcapellaBundle(
            url: url,
            song: song,
            recordedParts: recordedParts
        )
    }

    /// Save an .acapella bundle to a directory URL
    static func save(
        song: Song,
        recordedParts: [String: URL],
        to url: URL
    ) throws {
        let fm = FileManager.default

        // Create the bundle directory
        try fm.createDirectory(at: url, withIntermediateDirectories: true)

        // Write song.txt
        let songText = SongFileWriter.write(song: song)
        try songText.write(
            to: url.appendingPathComponent("song.txt"),
            atomically: true,
            encoding: .utf8
        )

        // Copy recorded audio files
        for (partName, sourceURL) in recordedParts {
            let destURL = url.appendingPathComponent("\(partName.lowercased()).wav")

            // Remove existing file if present
            if fm.fileExists(atPath: destURL.path) {
                try fm.removeItem(at: destURL)
            }

            try fm.copyItem(at: sourceURL, to: destURL)
        }
    }

    /// Export a mixed-down audio file of all recorded parts
    static func exportMix(
        from bundle: AcapellaBundle,
        to outputURL: URL
    ) throws {
        // This would use AVAudioEngine to mix all recorded WAV files
        // and write the result to outputURL
        // Implementation deferred to Phase 7
        fatalError("Mix export not yet implemented")
    }
}

// MARK: - Bundle Errors

enum BundleError: Error, LocalizedError {
    case notADirectory(String)
    case missingSongFile(String)
    case failedToCreateBundle(String)
    case failedToWriteAudio(String)

    var errorDescription: String? {
        switch self {
        case .notADirectory(let path):
            return "Not a directory: \(path)"
        case .missingSongFile(let path):
            return "No song.txt found in bundle: \(path)"
        case .failedToCreateBundle(let msg):
            return "Failed to create bundle: \(msg)"
        case .failedToWriteAudio(let msg):
            return "Failed to write audio: \(msg)"
        }
    }
}
