import Foundation
import MusicModel

// acasong-tool: convert v1 song files and validate .acasong files.
//
//   acasong-tool convert-v1 <input.txt> <output.acasong>
//   acasong-tool validate <file.acasong> [...]
//   acasong-tool info <file.acasong>

func fail(_ message: String) -> Never {
    FileHandle.standardError.write(Data((message + "\n").utf8))
    exit(1)
}

let args = CommandLine.arguments
guard args.count >= 2 else {
    fail("""
    usage:
      acasong-tool convert-v1 <input.txt> <output.acasong>
      acasong-tool validate <file.acasong> [...]
      acasong-tool info <file.acasong>
    """)
}

switch args[1] {
case "convert-v1":
    guard args.count == 4 else { fail("convert-v1 needs <input> and <output>") }
    do {
        let text = try String(contentsOfFile: args[2], encoding: .utf8)
        let score = try V1Converter.convert(text)
        try score.validate()
        let output = Acasong.serialize(score)
        // Round-trip check before writing.
        let reparsed = try Acasong.parse(output)
        try reparsed.validate()
        guard reparsed == score else { fail("round-trip mismatch after conversion") }
        try output.write(toFile: args[3], atomically: true, encoding: .utf8)
        print("✓ \(args[3]) — \(score.title): \(score.parts.count) parts, \(score.measureCount) measures")
    } catch {
        fail("✗ \(args[2]): \(error)")
    }

case "convert-musicxml":
    guard args.count == 4 else { fail("convert-musicxml needs <input.musicxml|.mxl> and <output.acasong>") }
    do {
        let data = try Data(contentsOf: URL(fileURLWithPath: args[2]))
        let baseName = URL(fileURLWithPath: args[2]).deletingPathExtension().lastPathComponent
        let result = try MusicXMLImporter.importScore(data: data, fileName: baseName)
        for warning in result.warnings {
            print("⚠ \(warning)")
        }
        let output = Acasong.serialize(result.score)
        let reparsed = try Acasong.parse(output)
        try reparsed.validate()
        try output.write(toFile: args[3], atomically: true, encoding: .utf8)
        print("✓ \(args[3]) — \(result.score.title): \(result.score.parts.count) parts, \(result.score.measureCount) measures")
    } catch {
        fail("✗ \(args[2]): \(error)")
    }

case "validate":
    guard args.count >= 3 else { fail("validate needs at least one file") }
    var failed = false
    for path in args.dropFirst(2) {
        do {
            let text = try String(contentsOfFile: path, encoding: .utf8)
            let score = try Acasong.parse(text)
            try score.validate()
            print("✓ \(path)")
        } catch {
            print("✗ \(path): \(error)")
            failed = true
        }
    }
    exit(failed ? 1 : 0)

case "info":
    guard args.count == 3 else { fail("info needs one file") }
    do {
        let text = try String(contentsOfFile: args[2], encoding: .utf8)
        let score = try Acasong.parse(text)
        try score.validate()
        print("title:    \(score.title)")
        if let composer = score.composer { print("composer: \(composer)") }
        print("time:     \(score.timeSignature.name)")
        print("key:      \(score.keySignature.name)")
        print("tempo:    \(score.tempoMap.baseBPM) bpm\(score.tempoMap.changes.count > 1 ? " (+\(score.tempoMap.changes.count - 1) changes)" : "")")
        print("measures: \(score.measureCount)\(score.hasPickup ? " (incl. pickup)" : "")")
        let seconds = score.tempoMap.seconds(atTick: score.endTick)
        print("duration: \(String(format: "%d:%04.1f", Int(seconds) / 60, seconds.truncatingRemainder(dividingBy: 60)))")
        for part in score.parts {
            let pitches = part.notes.compactMap(\.pitch)
            let range = pitches.isEmpty ? "—" : "\(pitches.min()!.name)–\(pitches.max()!.name)"
            print("  \(part.name) [\(part.clef.rawValue)]: \(part.notes.count) events, range \(range)")
        }
    } catch {
        fail("✗ \(args[2]): \(error)")
    }

default:
    fail("unknown command '\(args[1])'")
}
