import SwiftUI
import UniformTypeIdentifiers
import AVFoundation
import Combine

// MARK: - iOS App Entry Point

@main
struct AcapellaApp_iOS: App {
    @StateObject private var appState = AppState_iOS()

    var body: some Scene {
        WindowGroup {
            MainView_iOS()
                .environmentObject(appState)
        }
    }
}

// MARK: - iOS App State

/// Central state coordinator for the iOS app.
/// Replaces macOS NSOpenPanel/NSSavePanel with iOS file pickers and share sheets.
class AppState_iOS: 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          // True while processing

    /// Run pitch correction on a recorded part, updating recordedAudioURLs with the corrected file.
    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 {
                    // Fall back to original on error
                    self.recordedAudioURLs[partName] = originalURL
                    self.isCorrectingPitch = false
                }
            }
        }
    }

    /// Re-run pitch correction on all recorded parts (e.g. after strength change).
    func reapplyPitchCorrection() {
        for partName in originalAudioURLs.keys {
            if pitchCorrectionEnabled {
                applyPitchCorrection(partName: partName)
            } else {
                // Revert to originals
                recordedAudioURLs[partName] = originalAudioURLs[partName]
            }
        }
    }

    // MARK: - File import (replaces NSOpenPanel)

    /// Load a song from a file URL (called from .fileImporter)
    func loadSong(from url: URL) {
        // Security-scoped access for files from outside the app sandbox
        let accessing = url.startAccessingSecurityScopedResource()
        defer {
            if accessing { url.stopAccessingSecurityScopedResource() }
        }

        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)")
        }
    }

    /// Load an .acapella bundle from a directory URL.
    /// pickedFileURL: if the user picked a file (e.g. song.txt) inside the bundle,
    /// pass it here so we can use its security scope to access sibling files.
    func loadBundle(from url: URL, pickedFileURL: URL? = nil) {
        // Try security scope on both the directory and the originally picked file
        let accessingDir = url.startAccessingSecurityScopedResource()
        let accessingFile = pickedFileURL?.startAccessingSecurityScopedResource() ?? false
        defer {
            if accessingDir { url.stopAccessingSecurityScopedResource() }
            if accessingFile { pickedFileURL?.stopAccessingSecurityScopedResource() }
        }

        // Find the song .txt file in the bundle (could be "song.txt" or "<Title>.txt")
        let fm = FileManager.default
        let songURL: URL
        if let pickedFileURL = pickedFileURL, pickedFileURL.pathExtension == "txt" {
            songURL = pickedFileURL
        } else if fm.fileExists(atPath: url.appendingPathComponent("song.txt").path) {
            songURL = url.appendingPathComponent("song.txt")
        } else if let txtFile = try? fm.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
                    .first(where: { $0.pathExtension == "txt" }) {
            songURL = txtFile
        } else {
            print("No song .txt file 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 = [:]

                for part in song.parts {
                    let wavURL = url.appendingPathComponent("\(part.name.lowercased()).wav")
                    if FileManager.default.fileExists(atPath: wavURL.path) {
                        // Copy to app sandbox so we can access without security scope
                        let localURL = self.localAudioURL(forPart: part.name)
                        try? FileManager.default.removeItem(at: localURL)
                        try? FileManager.default.copyItem(at: wavURL, to: localURL)
                        self.recordedAudioURLs[part.name] = localURL
                        self.recordingStartMeasures[part.name] = 0
                    }
                }
                self.hasRecordedAudio = !self.recordedAudioURLs.isEmpty
            }
        } catch {
            print("Error loading bundle: \(error)")
        }
    }

    // MARK: - Save bundle to app Documents directory

    func saveBundle() -> URL? {
        guard let song = currentSong else { return nil }

        let fm = FileManager.default
        let docsDir = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
        let bundleDir = docsDir.appendingPathComponent("\(song.title).acapella")

        do {
            try fm.createDirectory(at: bundleDir, withIntermediateDirectories: true)

            let songText = SongFileWriter.write(song: song)
            let songFileName = "\(song.title).txt"
            try songText.write(to: bundleDir.appendingPathComponent(songFileName),
                             atomically: true, encoding: .utf8)

            for (partName, audioURL) in recordedAudioURLs {
                let destURL = bundleDir.appendingPathComponent("\(partName.lowercased()).wav")
                if fm.fileExists(atPath: destURL.path) {
                    try fm.removeItem(at: destURL)
                }
                try fm.copyItem(at: audioURL, to: destURL)
            }

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

    // MARK: - Export final mix

    func exportFinalMix(completion: @escaping (URL?) -> Void) {
        guard let song = currentSong else {
            completion(nil)
            return
        }

        DispatchQueue.global(qos: .userInitiated).async {
            let fm = FileManager.default
            let tempDir = fm.temporaryDirectory
            let outputURL = tempDir.appendingPathComponent("\(song.title).wav")

            // Remove any previous export
            try? fm.removeItem(at: outputURL)

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

    // MARK: - Close / Clear

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

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

    // MARK: - Helpers

    /// Local sandbox URL for a part's recorded audio
    private func localAudioURL(forPart partName: String) -> URL {
        let tempDir = FileManager.default.temporaryDirectory
        return tempDir.appendingPathComponent("\(partName.lowercased())_recording.wav")
    }
}

// NOTE: SongFileWriter is in Utilities/SongFileWriter.swift (shared between macOS and iOS)
