import Foundation

// MARK: - Parse Errors

/// Errors that can occur when parsing a song file.
enum ParseError: Error, LocalizedError {
    case emptyFile
    case missingTitle
    case invalidMetadataLine(String)
    case invalidTimeSignature(String)
    case invalidKeySignature(String)
    case invalidTempo(String)
    case noParts
    case invalidPartLine(lineNumber: Int, content: String)
    case invalidNote(lineNumber: Int, token: String)
    case invalidDuration(lineNumber: Int, token: String)
    case incompleteMeasure(partName: String, measureIndex: Int, expected: Double, got: Double)
    case fileNotFound(String)
    case readError(String)

    var errorDescription: String? {
        switch self {
        case .emptyFile:
            return "The file is empty."
        case .missingTitle:
            return "Missing song title on the first line."
        case .invalidMetadataLine(let line):
            return "Invalid metadata line: \"\(line)\". Expected format: \"4/4, C major, 120 bpm\""
        case .invalidTimeSignature(let str):
            return "Invalid time signature: \"\(str)\". Expected format like \"4/4\" or \"3/4\"."
        case .invalidKeySignature(let str):
            return "Invalid key signature: \"\(str)\". Expected format like \"C major\" or \"Bb minor\"."
        case .invalidTempo(let str):
            return "Invalid tempo: \"\(str)\". Expected format like \"120 bpm\"."
        case .noParts:
            return "No parts found in the file. Expected at least one line with a part name and notes."
        case .invalidPartLine(let line, let content):
            return "Invalid part line \(line): \"\(content)\""
        case .invalidNote(let line, let token):
            return "Invalid note on line \(line): \"\(token)\". Expected format like \"C4\", \"F#5\", \"Bb3\", or \"R\" for rest."
        case .invalidDuration(let line, let token):
            return "Invalid duration on line \(line): \"\(token)\". Expected 1, 2, 4, 8, or 16, optionally followed by a dot."
        case .incompleteMeasure(let part, let measure, let expected, let got):
            return "Incomplete measure \(measure + 1) in part \"\(part)\": expected \(expected) beats, got \(got) beats."
        case .fileNotFound(let path):
            return "File not found: \(path)"
        case .readError(let msg):
            return "Error reading file: \(msg)"
        }
    }
}
