import Foundation
import SwiftData

@MainActor
final class LibraryStore {
    let modelContext: ModelContext

    init(modelContext: ModelContext) {
        self.modelContext = modelContext
    }

    func settings() -> AppSettings {
        let descriptor = FetchDescriptor<AppSettings>()
        if let existing = try? modelContext.fetch(descriptor).first {
            return existing
        }
        let created = AppSettings()
        modelContext.insert(created)
        try? modelContext.save()
        return created
    }

    func seedLibraryIfNeeded() {
        let s = settings()
        do {
            try FileLibrary.ensureLibraryRoot()
        } catch {
            return
        }
        if !s.librarySeeded {
            s.librarySeeded = true
            try? modelContext.save()
        }
    }

    func catalogByPath() -> [String: CatalogEntry] {
        let all = (try? modelContext.fetch(FetchDescriptor<CatalogEntry>())) ?? []
        var map: [String: CatalogEntry] = [:]
        for entry in all {
            map[entry.relativePath] = entry
        }
        return map
    }

    func entry(id: String) -> CatalogEntry? {
        var descriptor = FetchDescriptor<CatalogEntry>(
            predicate: #Predicate { $0.id == id }
        )
        descriptor.fetchLimit = 1
        return try? modelContext.fetch(descriptor).first
    }

    func listDirectory(relative: String) throws -> DirListing {
        var listing = try FileLibrary.listDirectory(relative: relative)
        let map = catalogByPath()
        listing.entries = listing.entries.map { entry in
            var copy = entry
            if !entry.isDir {
                copy.resource = map[entry.relativePath]
            }
            return copy
        }
        return listing
    }

    @discardableResult
    func upsertCatalog(forFileURL url: URL, relativePath: String, kind: ResourceKind) -> CatalogEntry {
        let map = catalogByPath()
        if let existing = map[relativePath] {
            existing.kind = kind
            existing.name = url.deletingPathExtension().lastPathComponent
            try? modelContext.save()
            return existing
        }
        let entry = CatalogEntry(
            name: url.deletingPathExtension().lastPathComponent,
            kind: kind,
            relativePath: relativePath
        )
        modelContext.insert(entry)
        try? modelContext.save()
        return entry
    }

    /// Copy an external file into library under targetDir, optionally preserving nested relative path.
    @discardableResult
    func importFile(
        from source: URL,
        targetDir: String,
        nestedRelativePath: String? = nil
    ) throws -> CatalogEntry {
        try FileLibrary.ensureLibraryRoot()
        let dir = try FileLibrary.assertSafeRelative(targetDir)
        let nested = try FileLibrary.assertSafeRelative(nestedRelativePath ?? source.lastPathComponent)
        let nestedDir = (nested as NSString).deletingLastPathComponent
        let fileName = (nested as NSString).lastPathComponent
        let destDirRel: String
        if nestedDir.isEmpty || nestedDir == "." {
            destDirRel = dir
        } else if dir.isEmpty {
            destDirRel = nestedDir
        } else {
            destDirRel = "\(dir)/\(nestedDir)"
        }
        let destDirURL = try FileLibrary.resolveURL(relative: destDirRel)
        try FileManager.default.createDirectory(at: destDirURL, withIntermediateDirectories: true)

        let kind = ResourceKind.from(url: source)
        let destURL = FileLibrary.uniqueDestination(in: destDirURL, fileName: fileName)
        let accessing = source.startAccessingSecurityScopedResource()
        defer {
            if accessing { source.stopAccessingSecurityScopedResource() }
        }
        if FileManager.default.fileExists(atPath: destURL.path) {
            try FileManager.default.removeItem(at: destURL)
        }
        try FileManager.default.copyItem(at: source, to: destURL)
        let rel = try FileLibrary.relativePath(for: destURL)
        return upsertCatalog(forFileURL: destURL, relativePath: rel, kind: kind)
    }

    func createFolder(parentDir: String, name: String) throws -> String {
        try FileLibrary.createFolder(parentDir: parentDir, name: name)
    }

    func rename(relativePath: String, newName: String) throws {
        let oldRel = try FileLibrary.assertSafeRelative(relativePath)
        let newRel = try FileLibrary.rename(relativePath: oldRel, newName: newName)
        rewriteCatalogPaths(from: oldRel, to: newRel)
        try modelContext.save()
    }

    func move(relativePath: String, targetDir: String) throws {
        let oldRel = try FileLibrary.assertSafeRelative(relativePath)
        let newRel = try FileLibrary.move(relativePath: oldRel, targetDir: targetDir)
        rewriteCatalogPaths(from: oldRel, to: newRel)
        try modelContext.save()
    }

    func delete(relativePath: String) throws {
        let rel = try FileLibrary.assertSafeRelative(relativePath)
        try FileLibrary.delete(relativePath: rel)
        let all = (try? modelContext.fetch(FetchDescriptor<CatalogEntry>())) ?? []
        for entry in all where entry.relativePath == rel || entry.relativePath.hasPrefix(rel + "/") {
            modelContext.delete(entry)
        }
        try modelContext.save()
    }

    private func rewriteCatalogPaths(from oldRel: String, to newRel: String) {
        let all = (try? modelContext.fetch(FetchDescriptor<CatalogEntry>())) ?? []
        for entry in all {
            if entry.relativePath == oldRel {
                entry.relativePath = newRel
                entry.name = (newRel as NSString).deletingPathExtension
                if let base = (newRel as NSString).lastPathComponent as String? {
                    entry.name = (base as NSString).deletingPathExtension
                }
            } else if entry.relativePath.hasPrefix(oldRel + "/") {
                let suffix = String(entry.relativePath.dropFirst(oldRel.count))
                entry.relativePath = newRel + suffix
            }
        }
    }

    func purgeOrphanCatalogRows() -> Int {
        let all = (try? modelContext.fetch(FetchDescriptor<CatalogEntry>())) ?? []
        var removed = 0
        for entry in all {
            let url = try? FileLibrary.resolveURL(relative: entry.relativePath)
            if url == nil || !FileManager.default.fileExists(atPath: url!.path) {
                modelContext.delete(entry)
                removed += 1
            }
        }
        try? modelContext.save()
        return removed
    }

    func setActiveSession(_ session: StudySession?) {
        let s = settings()
        let all = (try? modelContext.fetch(FetchDescriptor<StudySession>())) ?? []
        for item in all {
            item.isActive = (session != nil && item.id == session?.id)
        }
        s.activeSessionId = session?.id
        try? modelContext.save()
    }

    func createSession(
        title: String,
        mode: StudyMode,
        resourceIds: SessionResources,
        albumId: String? = nil,
        albumIndex: Int? = nil
    ) -> StudySession {
        let session = StudySession(
            title: title,
            mode: mode,
            resourceIds: resourceIds,
            albumId: albumId,
            albumIndex: albumIndex,
            isActive: true
        )
        modelContext.insert(session)
        setActiveSession(session)
        return session
    }

    func deleteSession(_ session: StudySession) {
        let s = settings()
        if s.activeSessionId == session.id {
            s.activeSessionId = nil
        }
        ChatHistoryStore.remove(sessionId: session.id)
        modelContext.delete(session)
        try? modelContext.save()
    }

    func saveAlbum(_ album: Album) {
        album.updatedAt = Date()
        if modelContext.model(for: album.persistentModelID) as? Album == nil {
            modelContext.insert(album)
        }
        try? modelContext.save()
    }

    func deleteAlbum(_ album: Album) {
        modelContext.delete(album)
        try? modelContext.save()
    }

    func fileURL(for entry: CatalogEntry) -> URL? {
        try? FileLibrary.resolveURL(relative: entry.relativePath)
    }

    func fileURL(forResourceId id: String?) -> URL? {
        guard let id, let entry = entry(id: id) else { return nil }
        return fileURL(for: entry)
    }
}
