import Foundation
import ImageIO
import UIKit
import UniformTypeIdentifiers

/// Memory-efficient image resizer for upload preprocessing.
///
/// Why not `UIImage.jpegData(compressionQuality:)` directly? UIImage decodes
/// the entire source bitmap into RAM before re-encoding — a 12 MP iPhone
/// photo decompresses to ~50 MB regardless of how big the JPEG is. With
/// multiple selected images this can blow up memory on older devices.
///
/// `CGImageSourceCreateThumbnailAtIndex` reads the source incrementally and
/// only allocates the *resized* bitmap, paying memory proportional to the
/// output size (a 2048×1536 thumbnail is ~12 MB, but the source is never
/// fully decoded).
///
/// Reference: https://nshipster.com/image-resizing/ — section "Image I/O".
enum ImageDownsampler {

    /// Settings tuned per upload category. Edit here, not at call sites.
    struct Profile {
        /// Largest dimension (in points-after-scale, i.e. pixels) of the
        /// downsampled image. The shorter edge is set proportionally.
        let maxPixelEdge: CGFloat

        /// JPEG quality, 0...1. 0.85 is the WhatsApp / Slack default —
        /// visually indistinguishable from quality 1.0 at typical viewing
        /// sizes; cuts file size by ~3x.
        let jpegQuality: CGFloat

        static let completion = Profile(maxPixelEdge: 2048, jpegQuality: 0.85)
        static let documentImage = Profile(maxPixelEdge: 2560, jpegQuality: 0.90)
    }

    /// Downsample raw image bytes (e.g. from a `PHPickerResult`'s
    /// `loadDataRepresentation`) into a JPEG `Data` ready for upload.
    ///
    /// - Returns: encoded JPEG bytes, or nil if decoding failed.
    static func downsample(data: Data, profile: Profile) -> Data? {
        let options: [CFString: Any] = [
            kCGImageSourceShouldCache: false, // don't keep the full image around
            kCGImageSourceTypeIdentifierHint: UTType.jpeg.identifier as CFString, // best-effort hint
        ]
        guard let source = CGImageSourceCreateWithData(data as CFData, options as CFDictionary) else {
            return nil
        }
        return downsample(source: source, profile: profile)
    }

    /// Downsample from a file URL (e.g. PhotosPicker's
    /// `loadFileRepresentation`). Avoids materializing the full image in
    /// memory before resize.
    static func downsample(url: URL, profile: Profile) -> Data? {
        let options: [CFString: Any] = [
            kCGImageSourceShouldCache: false,
        ]
        guard let source = CGImageSourceCreateWithURL(url as CFURL, options as CFDictionary) else {
            return nil
        }
        return downsample(source: source, profile: profile)
    }

    /// Convenience for callers that already have a `UIImage` (e.g. from
    /// `UIImagePickerController`). We round-trip through PNG to get raw
    /// data, then use the data path. Slightly less efficient than starting
    /// from URL/Data, but still avoids the JPEG re-encode penalty for the
    /// resize step itself.
    static func downsample(uiImage: UIImage, profile: Profile) -> Data? {
        // Use PNG for the intermediate to avoid double-JPEG quality loss.
        // Even though PNG is larger, this stays in memory only briefly.
        guard let intermediate = uiImage.pngData() else { return nil }
        return downsample(data: intermediate, profile: profile)
    }

    // MARK: - Internal

    private static func downsample(source: CGImageSource, profile: Profile) -> Data? {
        // Compute the max pixel size in screen-resolution-aware units. We
        // use a fixed pixel cap because uploads are about bytes, not display.
        let scale: CGFloat = 1.0
        let maxDimensionInPixels = profile.maxPixelEdge * scale

        let downsampleOptions: [CFString: Any] = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceShouldCacheImmediately: true, // decode on the calling thread
            kCGImageSourceCreateThumbnailWithTransform: true, // honor EXIF orientation
            kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels,
        ]

        guard let cgImage = CGImageSourceCreateThumbnailAtIndex(
            source, 0, downsampleOptions as CFDictionary
        ) else {
            return nil
        }

        let uiImage = UIImage(cgImage: cgImage)
        return uiImage.jpegData(compressionQuality: profile.jpegQuality)
    }
}
