import Foundation

final class DocumentsModel: ObservableObject {
    static var shared = DocumentsModel()

    @Published private(set) var refreshID = UUID()

    typealias AreInIncreasingOrder = (URL, URL) -> Bool

    private var fileManager: FileManager {
        .default
    }

    var sortPredicates: [AreInIncreasingOrder] {
        [
            { self.isDirectory($0) && !self.isDirectory($1) },
            { $0.lastPathComponent.caseInsensitiveCompare($1.lastPathComponent) == .orderedAscending }
        ]
    }

    func sortedDirectoryContents(_ directoryURL: URL) -> [URL] {
        directoryContents(directoryURL).sorted { lhs, rhs in
            for predicate in sortPredicates {
                if !predicate(lhs, rhs), !predicate(rhs, lhs) {
                    continue
                }

                return predicate(lhs, rhs)
            }

            return false
        }
    }

    func directoryContents(_ directoryURL: URL) -> [URL] {
        contents(of: directoryURL)
    }

    var documentsDirectory: URL? {
        if let url = try? fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) {
            return standardizedURL(url)
        }
        return nil
    }

    func recentDocuments(_ limit: Int = 10) -> [URL] {
        guard let documentsDirectory else { return [] }

        return Array(
            contents(of: documentsDirectory)
                .filter { !isDirectory($0) }
                .sorted {
                    ((try? $0.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date()) >
                        ((try? $1.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date())
                }
                .prefix(limit)
        )
    }

    func isDocument(_ video: Video) -> Bool {
        guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return false }
        return isDocument(url)
    }

    func isDocument(_ url: URL) -> Bool {
        guard let url = standardizedURL(url), let documentsDirectory else { return false }
        return url.absoluteString.starts(with: documentsDirectory.absoluteString)
    }

    func isDirectory(_ url: URL) -> Bool {
        (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
    }

    var creationDateFormatter: DateFormatter {
        let formatter = DateFormatter()

        formatter.setLocalizedDateFormatFromTemplate("YYMMddHHmm")

        return formatter
    }

    func creationDate(_ video: Video) -> Date? {
        guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return nil }
        return creationDate(url)
    }

    func creationDate(_ url: URL) -> Date? {
        try? url.resourceValues(forKeys: [.creationDateKey]).creationDate
    }

    func formattedCreationDate(_ video: Video) -> String? {
        guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return nil }
        return formattedCreationDate(url)
    }

    func formattedCreationDate(_ url: URL) -> String? {
        if let date = try? url.resourceValues(forKeys: [.creationDateKey]).creationDate {
            return creationDateFormatter.string(from: date)
        }

        return nil
    }

    var sizeFormatter: ByteCountFormatter {
        let formatter = ByteCountFormatter()

        formatter.allowedUnits = .useAll
        formatter.countStyle = .file
        formatter.includesUnit = true
        formatter.isAdaptive = true

        return formatter
    }

    func size(_ video: Video) -> Int? {
        guard video.isLocal, let url = video.localStream?.localURL, let url = standardizedURL(url) else { return nil }
        return size(url)
    }

    func size(_ url: URL) -> Int? {
        try? url.resourceValues(forKeys: [.fileAllocatedSizeKey]).fileAllocatedSize
    }

    func formattedSize(_ video: Video) -> String? {
        guard let size = size(video) else { return nil }
        return sizeFormatter.string(fromByteCount: Int64(size))
    }

    func formattedSize(_ url: URL) -> String? {
        guard let size = size(url) else { return nil }
        return sizeFormatter.string(fromByteCount: Int64(size))
    }

    func removeDocument(_ url: URL) throws {
        guard isDocument(url) else { return }
        try fileManager.removeItem(at: url)
        URLBookmarkModel.shared.removeBookmark(url)
        refresh()
    }

    private func contents(of directory: URL) -> [URL] {
        (try? fileManager.contentsOfDirectory(
            at: directory,
            includingPropertiesForKeys: [.creationDateKey, .fileAllocatedSizeKey, .isDirectoryKey],
            options: [.includesDirectoriesPostOrder, .skipsHiddenFiles]
        )) ?? []
    }

    func displayLabelForDocument(_ file: URL) -> String {
        let components = file.absoluteString.components(separatedBy: "/Documents/")
        if components.count == 2 {
            let component = components[1]
            return component.isEmpty ? "Documents" : component.removingPercentEncoding ?? component
        }
        return "Document"
    }

    func standardizedURL(_ url: URL) -> URL? {
        let standardizedURL = NSString(string: url.absoluteString).standardizingPath
        return URL(string: standardizedURL)
    }

    func refresh() {
        refreshID = UUID()
    }
}