import SwiftUI
import Combine
import UniformTypeIdentifiers
import AVFoundation

// MARK: - App Entry Point

@main
struct AcapellaApp: App {
    @StateObject private var appState = AppState()

    var body: some Scene {
        Window("Acapella", id: "main") {
            MainWindow()
                .environmentObject(appState)
        }
        .commands {
            // File menu
            CommandGroup(replacing: .newItem) {
                Button("Open Song...") {
                    appState.openSongFile()
                }
                .keyboardShortcut("o", modifiers: .command)

                Divider()

                Button("Close Song") {
                    appState.closeSong()
                }
                .keyboardShortcut("w", modifiers: .command)
            }

            // Audio menu
            CommandMenu("Audio") {
                Button("Open Audio Bundle...") {
                    appState.openAudioBundle()
                }
                .keyboardShortcut("o", modifiers: [.command, .shift])

                Button("Save Audio Bundle...") {
                    appState.saveAudioBundle()
                }
                .keyboardShortcut("s", modifiers: .command)
                .disabled(!appState.hasRecordedAudio)

                Divider()

                Button("Export Final Mix...") {
                    appState.exportFinalMix()
                }
                .keyboardShortcut("e", modifiers: .command)
                .disabled(!appState.hasRecordedAudio)

                Divider()

                Toggle("Count-In (1 measure)", isOn: $appState.countInEnabled)
                    .keyboardShortcut("k", modifiers: .command)

                Toggle("Monitor Recording Part", isOn: $appState.monitorRecordingPart)

                Toggle("Metronome During Recording", isOn: $appState.metronomeDuringRecording)

                Divider()

                Toggle("Pitch Correction", isOn: $appState.pitchCorrectionEnabled)

                Divider()

                Button("Close Audio") {
                    appState.closeAudio()
                }
                .disabled(!appState.hasRecordedAudio)
            }
        }
    }
}

// MARK: - App State

/// Central state coordinator for the app.
/// Manages the document, file operations, and coordinates between subsystems.
class AppState: ObservableObject {
    @Published var currentSong: Song?
    @Published var hasRecordedAudio: Bool = false
    @Published var bundleURL: URL?
    @Published var countInEnabled: Bool = false
    @Published var monitorRecordingPart: Bool = false
    @Published var metronomeDuringRecording: Bool = false

    /// Recorded audio data keyed by part name (points to corrected files when pitch correction is on)
    var recordedAudioURLs: [String: URL] = [:]
    /// Original (uncorrected) recordings — never overwritten
    var originalAudioURLs: [String: URL] = [:]
    /// Starting measure index for each recording (for seek alignment)
    var recordingStartMeasures: [String: Int] = [:]

    // MARK: - Pitch Correction

    @Published var pitchCorrectionEnabled: Bool = true
    @Published var pitchCorrectionStrength: Double = 0.5   // 0.0 to 1.0
    @Published var isCorrectingPitch: Bool = false

    func applyPitchCorrection(partName: String) {
        guard pitchCorrectionEnabled,
              let song = currentSong,
              let part = song.parts.first(where: { $0.name == partName }),
              let originalURL = originalAudioURLs[partName] else { return }

        let startMeasure = recordingStartMeasures[partName] ?? 0
        let config = PitchCorrector.Configuration(correctionStrength: pitchCorrectionStrength)

        DispatchQueue.main.async { self.isCorrectingPitch = true }

        DispatchQueue.global(qos: .userInitiated).async {
            do {
                let correctedURL = try PitchCorrector.correct(
                    inputURL: originalURL,
                    part: part,
                    tempo: song.defaultTempo,
                    startMeasure: startMeasure,
                    configuration: config
                )
                DispatchQueue.main.async {
                    self.recordedAudioURLs[partName] = correctedURL
                    self.isCorrectingPitch = false
                }
            } catch {
                print("[PitchCorrector] Error: \(error)")
                DispatchQueue.main.async {
                    self.recordedAudioURLs[partName] = originalURL
                    self.isCorrectingPitch = false
                }
            }
        }
    }

    func reapplyPitchCorrection() {
        for partName in originalAudioURLs.keys {
            if pitchCorrectionEnabled {
                applyPitchCorrection(partName: partName)
            } else {
                recordedAudioURLs[partName] = originalAudioURLs[partName]
            }
        }
    }

    // MARK: - File operations

    func openSongFile() {
        let panel = NSOpenPanel()
        panel.allowedContentTypes = [.plainText]
        panel.allowsMultipleSelection = false
        panel.title = "Open Song File"

        panel.begin { [weak self] response in
            guard response == .OK, let url = panel.url else { return }
            self?.loadSong(from: url)
        }
    }

    func loadSong(from url: URL) {
        do {
            let song = try SongFileParser.parse(url: url)
            DispatchQueue.main.async {
                self.currentSong = song
                self.recordedAudioURLs = [:]
                self.recordingStartMeasures = [:]
                self.hasRecordedAudio = false
                self.bundleURL = nil
            }
        } catch {
            print("Error loading song: \(error)")
        }
    }

    func openAudioBundle() {
        let panel = NSOpenPanel()
        panel.canChooseDirectories = true
        panel.canChooseFiles = false
        panel.allowsMultipleSelection = false
        panel.title = "Open Acapella Bundle"
        panel.message = "Select an .acapella folder"

        panel.begin { [weak self] response in
            guard response == .OK, let url = panel.url else { return }
            self?.loadBundle(from: url)
        }
    }

    func loadBundle(from url: URL) {
        // Look for song.txt in the bundle
        let songURL = url.appendingPathComponent("song.txt")
        guard FileManager.default.fileExists(atPath: songURL.path) else {
            print("No song.txt found in bundle")
            return
        }

        do {
            let song = try SongFileParser.parse(url: songURL)
            DispatchQueue.main.async {
                self.currentSong = song
                self.bundleURL = url
                self.recordedAudioURLs = [:]
                self.recordingStartMeasures = [:]

                // Look for recorded WAV files (assumed to start at measure 0 when loaded from bundle)
                for part in song.parts {
                    let wavURL = url.appendingPathComponent("\(part.name.lowercased()).wav")
                    if FileManager.default.fileExists(atPath: wavURL.path) {
                        self.recordedAudioURLs[part.name] = wavURL
                        self.recordingStartMeasures[part.name] = 0
                    }
                }
                self.hasRecordedAudio = !self.recordedAudioURLs.isEmpty
            }
        } catch {
            print("Error loading bundle: \(error)")
        }
    }

    func saveAudioBundle() {
        guard let song = currentSong else { return }

        let panel = NSSavePanel()
        panel.title = "Save Acapella Bundle"
        panel.nameFieldStringValue = "\(song.title).acapella"
        panel.canCreateDirectories = true

        panel.begin { [weak self] response in
            guard response == .OK, let url = panel.url else { return }
            self?.saveBundle(to: url)
        }
    }

    func saveBundle(to url: URL) {
        guard let song = currentSong else { return }

        do {
            // Create the bundle directory
            try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)

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

            // Copy recorded audio files
            for (partName, audioURL) in recordedAudioURLs {
                let destURL = url.appendingPathComponent("\(partName.lowercased()).wav")
                if FileManager.default.fileExists(atPath: destURL.path) {
                    try FileManager.default.removeItem(at: destURL)
                }
                try FileManager.default.copyItem(at: audioURL, to: destURL)
            }

            DispatchQueue.main.async {
                self.bundleURL = url
            }
        } catch {
            print("Error saving bundle: \(error)")
        }
    }

    func closeAudio() {
        recordedAudioURLs = [:]
        recordingStartMeasures = [:]
        hasRecordedAudio = false
    }

    func closeSong() {
        currentSong = nil
        recordedAudioURLs = [:]
        recordingStartMeasures = [:]
        hasRecordedAudio = false
        bundleURL = nil
    }

    // MARK: - Export

    func exportFinalMix() {
        guard let song = currentSong else { return }

        let panel = NSSavePanel()
        panel.title = "Export Final Mix"
        panel.nameFieldStringValue = "\(song.title).wav"
        panel.allowedContentTypes = [.wav]

        panel.begin { [weak self] response in
            guard response == .OK, let url = panel.url, let self = self else { return }

            DispatchQueue.global(qos: .userInitiated).async {
                do {
                    let engine = AudioEngine()
                    try engine.exportMix(
                        song: song,
                        tempo: song.defaultTempo,
                        recordedAudioURLs: self.recordedAudioURLs,
                        recordingStartMeasures: self.recordingStartMeasures,
                        to: url
                    )
                    print("[Export] Successfully exported to \(url.lastPathComponent)")
                } catch {
                    print("[Export] Error: \(error)")
                }
            }
        }
    }
}

// NOTE: SongFileWriter has been moved to Utilities/SongFileWriter.swift (shared between macOS and iOS)
