import Foundation
import Capacitor
import Photos

// Copies a downloaded photo/video into the user's Photos library, inside a named album ("Connect"), the
// way WhatsApp puts saved media in a WhatsApp album. Registered by cap sync as a real Capacitor plugin
// package, so it appears as window.Capacitor.Plugins.MediaLibrary (an app-embedded class would be
// stripped in release builds).
//
// WHY A PLUGIN AT ALL: the app already saves downloads into its own Documents folder (visible in Files),
// but Files is not where people look for photos and videos — the Photos app is, and only PhotoKit can put
// something there. @capacitor/filesystem cannot: an app's sandbox and the photo library are separate stores.
//
// PERMISSION NOTE: .addOnly is enough to add an asset, but NOT to look up or create an ALBUM — that needs
// .readWrite. So we request .readWrite, and both NSPhotoLibraryUsageDescription and
// NSPhotoLibraryAddUsageDescription must be present (ios-patch.sh sets them).
@objc(MediaLibraryPlugin)
public class MediaLibraryPlugin: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "MediaLibraryPlugin"
    public let jsName = "MediaLibrary"
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "saveToAlbum", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "checkPermission", returnType: CAPPluginReturnPromise)
    ]

    // MARK: - API

    @objc func checkPermission(_ call: CAPPluginCall) {
        call.resolve(["status": MediaLibraryPlugin.label(PHPhotoLibrary.authorizationStatus(for: .readWrite))])
    }

    @objc func saveToAlbum(_ call: CAPPluginCall) {
        guard let raw = call.getString("path"), !raw.isEmpty else {
            call.reject("path is required"); return
        }
        let album = (call.getString("album") ?? "Connect").trimmingCharacters(in: .whitespacesAndNewlines)
        let url = MediaLibraryPlugin.fileURL(from: raw)
        guard FileManager.default.fileExists(atPath: url.path) else {
            call.reject("file not found: \(url.path)"); return
        }
        // Trust an explicit kind if the web layer passed one (it knows the MIME); otherwise fall back to
        // the file extension.
        let isVideo: Bool = {
            if let kind = call.getString("kind") { return kind == "video" }
            return MediaLibraryPlugin.videoExtensions.contains(url.pathExtension.lowercased())
        }()

        PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
            guard status == .authorized || status == .limited else {
                call.reject("permission denied", MediaLibraryPlugin.label(status)); return
            }
            self.album(named: album) { collection in
                PHPhotoLibrary.shared().performChanges({
                    // addResource(with:fileURL:) works uniformly for photo and video and, unlike the
                    // creationRequestForAssetFrom* helpers, is non-optional — no silent no-op path.
                    let request = PHAssetCreationRequest.forAsset()
                    let options = PHAssetResourceCreationOptions()
                    options.shouldMoveFile = false          // keep our own copy in the app folder
                    options.originalFilename = url.lastPathComponent
                    request.addResource(with: isVideo ? .video : .photo, fileURL: url, options: options)
                    // If the album couldn't be resolved (e.g. "limited" access), still save the asset —
                    // landing in the camera roll beats failing outright.
                    if let collection = collection,
                       let placeholder = request.placeholderForCreatedAsset,
                       let add = PHAssetCollectionChangeRequest(for: collection) {
                        add.addAssets([placeholder] as NSArray)
                    }
                }) { ok, err in
                    if ok {
                        call.resolve(["saved": true, "album": album, "inAlbum": collection != nil])
                    } else {
                        call.reject(err?.localizedDescription ?? "could not save to Photos")
                    }
                }
            }
        }
    }

    // MARK: - Helpers

    private static let videoExtensions: Set<String> = ["mp4", "mov", "m4v", "3gp", "avi", "mkv", "webm"]

    private static func label(_ s: PHAuthorizationStatus) -> String {
        switch s {
        case .authorized: return "granted"
        case .limited:    return "limited"
        case .denied:     return "denied"
        case .restricted: return "restricted"
        case .notDetermined: return "prompt"
        @unknown default: return "unknown"
        }
    }

    // Accepts either a file:// URI (what Filesystem.writeFile returns) or a bare absolute path.
    private static func fileURL(from raw: String) -> URL {
        if raw.hasPrefix("file://") {
            if let u = URL(string: raw) { return u }
            // Un-encoded spaces make URL(string:) fail — percent-encode and retry before giving up.
            let encoded = raw.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? raw
            if let u = URL(string: encoded) { return u }
        }
        return URL(fileURLWithPath: raw)
    }

    /// Find the album by title, creating it the first time. Returns nil if it can't be resolved.
    private func album(named name: String, completion: @escaping (PHAssetCollection?) -> Void) {
        if let existing = MediaLibraryPlugin.findAlbum(name) { completion(existing); return }
        var placeholder: PHObjectPlaceholder?
        PHPhotoLibrary.shared().performChanges({
            let req = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
            placeholder = req.placeholderForCreatedAssetCollection
        }) { ok, _ in
            guard ok, let id = placeholder?.localIdentifier else {
                // A racing create (two downloads at once) means it exists now — look again before failing.
                completion(MediaLibraryPlugin.findAlbum(name)); return
            }
            completion(PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [id], options: nil).firstObject)
        }
    }

    private static func findAlbum(_ name: String) -> PHAssetCollection? {
        let opts = PHFetchOptions()
        opts.predicate = NSPredicate(format: "localizedTitle = %@", name)
        return PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: opts).firstObject
    }
}
