import UIKit

// MARK: - BlurHash Encoder

/// Pure Swift BlurHash encoder matching the reference implementation at https://blurha.sh/
/// Desktop parity: uses `encode(imageData.data, width, height, 4, 4)` from the `blurhash` npm package.
///
/// BlurHash encodes a compact representation of a placeholder for an image (typically 20-30 chars).
/// Used in attachment `preview` field: `"tag::blurhash"`.
enum BlurHashEncoder {

    /// Encodes a UIImage into a BlurHash string.
    ///
    /// - Parameters:
    ///   - image: Source image (will be downscaled internally for performance).
    ///   - numberOfComponents: AC components (x, y). Android parity: `BlurHash.encode(bitmap, 4, 3)`.
    /// - Returns: BlurHash string, or `nil` if encoding fails.
    nonisolated static func encode(image: UIImage, numberOfComponents components: (Int, Int) = (4, 3)) -> String? {
        let (componentX, componentY) = components
        guard componentX >= 1, componentX <= 9, componentY >= 1, componentY <= 9 else { return nil }

        // Downscale for performance (BlurHash doesn't need high resolution)
        let pixelWidth = 32
        let pixelHeight = 32

        guard let cgImage = image.cgImage else { return nil }
        let width = pixelWidth
        let height = pixelHeight

        let bytesPerRow = width * 4
        var pixels = [UInt8](repeating: 0, count: bytesPerRow * height)

        guard let context = CGContext(
            data: &pixels,
            width: width,
            height: height,
            bitsPerComponent: 8,
            bytesPerRow: bytesPerRow,
            space: CGColorSpaceCreateDeviceRGB(),
            bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
        ) else { return nil }

        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

        // Calculate DC and AC components
        var factors = [(Float, Float, Float)]()
        for j in 0..<componentY {
            for i in 0..<componentX {
                let factor = multiplyBasisFunction(pixels: pixels, width: width, height: height,
                                                   bytesPerRow: bytesPerRow,
                                                   componentX: i, componentY: j)
                factors.append(factor)
            }
        }

        // Encode DC value
        let dc = factors.first!
        let dcValue = encodeDC(dc)

        // Quantise maximum AC component value
        var maximumValue: Float = 0
        if factors.count > 1 {
            let actualMaximum = factors.dropFirst().reduce(Float(0)) { current, factor in
                max(current, abs(factor.0), abs(factor.1), abs(factor.2))
            }
            let quantisedMaximum = max(0, min(82, Int(floor(actualMaximum * 166 - 0.5))))
            maximumValue = Float(quantisedMaximum + 1) / 166
            // Size flag encodes number of components + max AC
            let sizeFlag = (componentX - 1) + (componentY - 1) * 9

            var result = ""
            result += encode83(value: sizeFlag, length: 1)
            result += encode83(value: quantisedMaximum, length: 1)
            result += encode83(value: dcValue, length: 4)

            for factor in factors.dropFirst() {
                result += encode83(value: encodeAC(factor, maximumValue: maximumValue), length: 2)
            }
            return result
        } else {
            let sizeFlag = (componentX - 1) + (componentY - 1) * 9
            var result = ""
            result += encode83(value: sizeFlag, length: 1)
            result += encode83(value: 0, length: 1)
            result += encode83(value: dcValue, length: 4)
            return result
        }
    }

    // MARK: - Basis Function

    private nonisolated static func multiplyBasisFunction(
        pixels: [UInt8], width: Int, height: Int, bytesPerRow: Int,
        componentX: Int, componentY: Int
    ) -> (Float, Float, Float) {
        var r: Float = 0
        var g: Float = 0
        var b: Float = 0
        let normalization: Float = (componentX == 0 && componentY == 0) ? 1 : 2

        for y in 0..<height {
            for x in 0..<width {
                let basis = normalization
                    * cos((Float.pi * Float(componentX) * Float(x)) / Float(width))
                    * cos((Float.pi * Float(componentY) * Float(y)) / Float(height))
                let offset = y * bytesPerRow + x * 4
                r += basis * sRGBToLinear(pixels[offset])
                g += basis * sRGBToLinear(pixels[offset + 1])
                b += basis * sRGBToLinear(pixels[offset + 2])
            }
        }

        let scale = 1.0 / Float(width * height)
        return (r * scale, g * scale, b * scale)
    }

    // MARK: - sRGB <-> Linear

    nonisolated static func sRGBToLinear(_ value: UInt8) -> Float {
        let v = Float(value) / 255
        if v <= 0.04045 {
            return v / 12.92
        } else {
            return pow((v + 0.055) / 1.055, 2.4)
        }
    }

    nonisolated static func linearToSRGB(_ value: Float) -> Int {
        let v = max(0, min(1, value))
        if v <= 0.0031308 {
            return Int(v * 12.92 * 255 + 0.5)
        } else {
            return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5)
        }
    }

    // MARK: - DC / AC Encoding

    private nonisolated static func encodeDC(_ value: (Float, Float, Float)) -> Int {
        let r = linearToSRGB(value.0)
        let g = linearToSRGB(value.1)
        let b = linearToSRGB(value.2)
        return (r << 16) + (g << 8) + b
    }

    private nonisolated static func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int {
        let r = max(0, min(18, Int(floor(signPow(value.0 / maximumValue) * 9 + 9.5))))
        let g = max(0, min(18, Int(floor(signPow(value.1 / maximumValue) * 9 + 9.5))))
        let b = max(0, min(18, Int(floor(signPow(value.2 / maximumValue) * 9 + 9.5))))
        return r * 19 * 19 + g * 19 + b
    }

    private nonisolated static func signPow(_ value: Float) -> Float {
        return copysign(pow(abs(value), 0.5), value)
    }

    // MARK: - Base83 Encoding

    private nonisolated static let base83Characters: [Character] = Array(
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
    )

    private nonisolated static func encode83(value: Int, length: Int) -> String {
        var result = ""
        for i in 1...length {
            let digit = (value / pow83(length - i)) % 83
            result.append(base83Characters[digit])
        }
        return result
    }

    private nonisolated static func pow83(_ exponent: Int) -> Int {
        var result = 1
        for _ in 0..<exponent {
            result *= 83
        }
        return result
    }
}

// MARK: - BlurHash Decoder

/// Pure Swift BlurHash decoder matching the canonical woltapp/blurhash reference implementation.
/// https://github.com/woltapp/blurhash/blob/master/Swift/BlurHashDecode.swift
///
/// Key differences from previous broken implementation:
/// - Length formula: `4 + 2 * numX * numY` (NOT `4 + 2 * (numX * numY - 1)` which was off by 2)
/// - Pixel format: 3 bytes/pixel RGB with CGImageAlphaInfo.none (canonical)
/// - Buffer: CFDataCreateMutable (canonical)
/// - Punch parameter for color intensity control (Android parity)
enum BlurHashDecoder {
    nonisolated private static let isNativeBlurHashDisabled = ProcessInfo.processInfo.environment["ROSETTA_DISABLE_NATIVE_BLURHASH"] == "1"

    /// Decodes a BlurHash string into a UIImage.
    ///
    /// - Parameters:
    ///   - blurHash: The BlurHash string to decode.
    ///   - width: Output image width in pixels (default 32 — small, blurred anyway).
    ///   - height: Output image height in pixels (default 32).
    ///   - punch: Color intensity multiplier (default 1). Android parity.
    /// - Returns: A UIImage placeholder, or `nil` if decoding fails.
    nonisolated static func decode(blurHash: String, width: Int = 32, height: Int = 32, punch: Float = 1) -> UIImage? {
        guard blurHash.count >= 6 else { return nil }

        if !isNativeBlurHashDisabled {
            if let nativeRGB = NativeBlurHashBridge.decodeBlurHash(
                blurHash,
                width: width,
                height: height,
                punch: punch
            ),
               let nativeImage = makeImageFromRGBData(nativeRGB, width: width, height: height) {
                return nativeImage
            }
        }

        let sizeFlag = decodeBase83(blurHash, from: 0, length: 1)
        let numY = (sizeFlag / 9) + 1
        let numX = (sizeFlag % 9) + 1

        // Canonical formula: 1(sizeFlag) + 1(maxVal) + 4(DC) + 2*(N-1)(AC) = 4 + 2*N
        let expectedLength = 4 + 2 * numX * numY
        guard blurHash.count == expectedLength else { return nil }

        let quantisedMaximumValue = decodeBase83(blurHash, from: 1, length: 1)
        let maximumValue = Float(quantisedMaximumValue + 1) / 166

        let colors: [(Float, Float, Float)] = (0..<numX * numY).map { i in
            if i == 0 {
                let value = decodeBase83(blurHash, from: 2, length: 4)
                return decodeDC(value)
            } else {
                let value = decodeBase83(blurHash, from: 4 + i * 2, length: 2)
                return decodeAC(value, maximumValue: maximumValue * punch)
            }
        }

        // Canonical: 3 bytes per pixel (RGB, no alpha), CFDataCreateMutable buffer
        let bytesPerRow = width * 3
        guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
        CFDataSetLength(data, bytesPerRow * height)
        guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }

        for y in 0..<height {
            for x in 0..<width {
                var r: Float = 0
                var g: Float = 0
                var b: Float = 0
                for j in 0..<numY {
                    for i in 0..<numX {
                        let basis = cos(Float.pi * Float(x) * Float(i) / Float(width))
                            * cos(Float.pi * Float(y) * Float(j) / Float(height))
                        let color = colors[i + j * numX]
                        r += color.0 * basis
                        g += color.1 * basis
                        b += color.2 * basis
                    }
                }
                let offset = 3 * x + y * bytesPerRow
                pixels[offset] = UInt8(clamping: BlurHashEncoder.linearToSRGB(r))
                pixels[offset + 1] = UInt8(clamping: BlurHashEncoder.linearToSRGB(g))
                pixels[offset + 2] = UInt8(clamping: BlurHashEncoder.linearToSRGB(b))
            }
        }

        return makeImageFromRGBData(data as Data, width: width, height: height)
    }

    // MARK: - DC / AC Decoding

    private nonisolated static func decodeDC(_ value: Int) -> (Float, Float, Float) {
        let r = value >> 16
        let g = (value >> 8) & 255
        let b = value & 255
        return (sRGBToLinear(r), sRGBToLinear(g), sRGBToLinear(b))
    }

    private nonisolated static func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
        let quantR = value / (19 * 19)
        let quantG = (value / 19) % 19
        let quantB = value % 19
        return (
            signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
            signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
            signPow((Float(quantB) - 9) / 9, 2) * maximumValue
        )
    }

    private nonisolated static func signPow(_ value: Float, _ exp: Float) -> Float {
        copysign(pow(abs(value), exp), value)
    }

    private nonisolated static func sRGBToLinear<T: BinaryInteger>(_ value: T) -> Float {
        let v = Float(Int64(value)) / 255
        if v <= 0.04045 { return v / 12.92 }
        else { return pow((v + 0.055) / 1.055, 2.4) }
    }

    // MARK: - Base83 Decoding (string-index based, matching canonical)

    private nonisolated static let base83Lookup: [Character: Int] = {
        let chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
        var lookup = [Character: Int]()
        for (i, ch) in chars.enumerated() {
            lookup[ch] = i
        }
        return lookup
    }()

    private nonisolated static func decodeBase83(_ string: String, from start: Int, length: Int) -> Int {
        let startIdx = string.index(string.startIndex, offsetBy: start)
        let endIdx = string.index(startIdx, offsetBy: length)
        var value = 0
        for char in string[startIdx..<endIdx] {
            value = value * 83 + (base83Lookup[char] ?? 0)
        }
        return value
    }

    private nonisolated static func makeImageFromRGBData(_ data: Data, width: Int, height: Int) -> UIImage? {
        let bytesPerRow = width * 3
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
        guard let provider = CGDataProvider(data: data as CFData) else { return nil }
        guard let cgImage = CGImage(
            width: width,
            height: height,
            bitsPerComponent: 8,
            bitsPerPixel: 24,
            bytesPerRow: bytesPerRow,
            space: CGColorSpaceCreateDeviceRGB(),
            bitmapInfo: bitmapInfo,
            provider: provider,
            decode: nil,
            shouldInterpolate: true,
            intent: .defaultIntent
        ) else { return nil }
        return UIImage(cgImage: cgImage)
    }
}

// MARK: - UIImage Extension

extension UIImage {
    /// Generates a BlurHash string from this image.
    /// Android parity: `BlurHash.encode(bitmap, 4, 3)`.
    func blurHash(numberOfComponents components: (Int, Int) = (4, 3)) -> String? {
        return BlurHashEncoder.encode(image: self, numberOfComponents: components)
    }

    /// Creates a UIImage from a BlurHash string.
    /// Canonical woltapp/blurhash decoder with punch parameter (Android parity).
    nonisolated static func fromBlurHash(_ blurHash: String, width: Int = 32, height: Int = 32, punch: Float = 1) -> UIImage? {
        return BlurHashDecoder.decode(blurHash: blurHash, width: width, height: height, punch: punch)
    }
}
