import SwiftUI
import UniformTypeIdentifiers
import SaplingCore
import SaplingWorkspace
import SaplingGit
import SaplingStorage
import SaplingLogging
import SaplingEditor
import SaplingRenderer
import SaplingUI

@main
struct SaplingApplication: App {
    @StateObject private var model = SaplingAppModel(dependencies: .live())

    var body: some Scene {
        WindowGroup {
            MainWindow(model: model)
        }
        #if os(macOS)
        .windowStyle(.titleBar)
        .windowToolbarStyle(.unified)
        #endif

        #if os(macOS)
        Settings {
            SettingsView(
                configuration: $model.configuration,
                onSave: model.save(configuration:)
            )
        }
        #endif
    }
}

@MainActor
private final class SaplingAppModel: ObservableObject {
    @Published var workspace: Workspace
    @Published var selectedProject: Project?
    @Published var selectedDocument: MarkdownDocument?
    @Published var editorViewModel: HybridMarkdownEditorViewModel?
    @Published var gitStatuses: [GitFileStatus] = []
    @Published var configuration: SaplingConfiguration
    @Published var editorErrorMessage: String?

    private let gitProvider: any GitProvider
    private let configurationStore: any ConfigurationStore
    private let workspaceManager: any WorkspaceManaging
    private let logger: SaplingLogger

    init(dependencies: AppDependencies) {
        self.gitProvider = dependencies.gitProvider
        self.configurationStore = dependencies.configurationStore
        self.workspaceManager = dependencies.workspaceManager
        self.logger = dependencies.logger
        self.configuration = (try? dependencies.configurationStore.loadConfiguration()) ?? SaplingConfiguration()

        let workspace = workspaceManager.sampleWorkspace()
        self.workspace = workspace
        self.selectedProject = workspace.firstProject
        setSelectedDocument(SaplingSampleData.document)

        logger.info("Sapling application model initialized", category: .app)

        Task {
            await refreshGitStatus()
        }
    }

    func select(file: WorkspaceFile) {
        guard file.kind == .markdown else { return }

        if file.url == SaplingSampleData.document.url {
            setSelectedDocument(SaplingSampleData.document)
        } else {
            openDocument(at: file.url)
        }
    }

    func openDocument(at url: URL) {
        let didStartAccessing = url.startAccessingSecurityScopedResource()
        defer {
            if didStartAccessing {
                url.stopAccessingSecurityScopedResource()
            }
        }

        do {
            let document = try HybridMarkdownEditorViewModel.loadDocument(at: url)
            setSelectedDocument(document)
            editorErrorMessage = nil
            logger.info("Opened document: \(document.title)", category: .editor)
        } catch {
            editorErrorMessage = "Unable to open \(url.lastPathComponent): \(error.localizedDescription)"
            logger.error("Failed to open document: \(error)", category: .editor)
        }
    }

    func saveSelectedDocument() {
        guard let editorViewModel else { return }

        do {
            try editorViewModel.save()
            selectedDocument = editorViewModel.document
            editorErrorMessage = nil
            logger.info("Saved document: \(editorViewModel.document.title)", category: .editor)
        } catch {
            editorErrorMessage = "Unable to save \(editorViewModel.document.title): \(error.localizedDescription)"
            logger.error("Failed to save document: \(error)", category: .editor)
        }
    }

    func select(project: Project) {
        selectedProject = project
        logger.info("Selected project: \(project.name)", category: .workspace)
        Task {
            await refreshGitStatus()
        }
    }

    func save(configuration: SaplingConfiguration) {
        do {
            try configurationStore.saveConfiguration(configuration)
            logger.debug("Saved configuration", category: .storage)
        } catch {
            logger.error("Failed to save configuration: \(error)", category: .storage)
        }
    }

    private func refreshGitStatus() async {
        guard let selectedProject else {
            gitStatuses = []
            return
        }

        do {
            let repository = gitProvider.repository(at: selectedProject.repositoryURL)
            gitStatuses = try await repository.status()
            logger.debug("Loaded \(gitStatuses.count) Git status entries", category: .git)
        } catch {
            gitStatuses = [
                GitFileStatus(path: "Unable to load status: \(error)", state: .conflicted)
            ]
            logger.error("Failed to refresh Git status: \(error)", category: .git)
        }
    }

    private func setSelectedDocument(_ document: MarkdownDocument) {
        selectedDocument = document
        editorViewModel = HybridMarkdownEditorViewModel(document: document)
        logger.info("Selected document: \(document.title)", category: .editor)
    }
}

private struct MainWindow: View {
    @ObservedObject var model: SaplingAppModel
    @State private var isImportingMarkdown = false

    var body: some View {
        NavigationSplitView {
            WorkspaceTreeView(
                workspace: model.workspace,
                onSelectFile: model.select(file:),
                onSelectProject: model.select(project:)
            )
            .frame(minWidth: 220)
        } content: {
            if let editorViewModel = model.editorViewModel {
                HybridMarkdownEditor(
                    viewModel: editorViewModel,
                    renderer: MarkdownRenderer()
                )
                .navigationTitle(navigationTitle(for: editorViewModel))
            } else {
                EmptyEditorPlaceholder()
            }
        } detail: {
            SaplingInspectorView(
                project: model.selectedProject,
                document: model.editorViewModel?.document ?? model.selectedDocument,
                statuses: model.gitStatuses
            )
            .frame(minWidth: 260)
        }
        .toolbar {
            ToolbarItemGroup {
                Button {
                    isImportingMarkdown = true
                } label: {
                    Label("Open Markdown", systemImage: "doc.badge.plus")
                }
                .help("Open Markdown")

                Button {
                    model.saveSelectedDocument()
                } label: {
                    Label("Save", systemImage: "square.and.arrow.down")
                }
                .help("Save")
                .disabled(model.editorViewModel?.hasUnsavedChanges != true)
                .keyboardShortcut("s", modifiers: .command)
            }
        }
        .fileImporter(
            isPresented: $isImportingMarkdown,
            allowedContentTypes: [.saplingMarkdown, .plainText],
            allowsMultipleSelection: false
        ) { result in
            switch result {
            case .success(let urls):
                guard let url = urls.first else { return }
                model.openDocument(at: url)
            case .failure(let error):
                model.editorErrorMessage = "Unable to open document: \(error.localizedDescription)"
            }
        }
        .alert(
            "Editor Error",
            isPresented: Binding(
                get: { model.editorErrorMessage != nil },
                set: { if !$0 { model.editorErrorMessage = nil } }
            )
        ) {
            Button("OK", role: .cancel) {
                model.editorErrorMessage = nil
            }
        } message: {
            Text(model.editorErrorMessage ?? "")
        }
    }

    private func navigationTitle(for viewModel: HybridMarkdownEditorViewModel) -> String {
        viewModel.hasUnsavedChanges ? "\(viewModel.document.title) *" : viewModel.document.title
    }
}

private extension UTType {
    static var saplingMarkdown: UTType {
        UTType(filenameExtension: "md") ?? .plainText
    }
}

private extension Workspace {
    var firstProject: Project? {
        for item in items {
            if let project = item.firstProject {
                return project
            }
        }
        return nil
    }
}

private extension WorkspaceItem {
    var firstProject: Project? {
        switch self {
        case .project(let project):
            return project
        case .folder(let folder):
            for child in folder.children {
                if let project = child.firstProject {
                    return project
                }
            }
            return nil
        case .file, .subproject:
            return nil
        }
    }
}
