// Based on https://github.com/aheze/VariableBlurView/blob/main/Sources/VariableBlurView.swift
import UIKit

public protocol ViewBlurring: UIView {
	var blurRadius: CGFloat? { get set }
}

extension UIView: ViewBlurring {
	private var blurRadiusKey: String { "inputRadius" }
	private var blurFilterName: String { "blurFilter" }
	private var blurKeyPath: String {
		"filters.\(blurFilterName).\(blurRadiusKey)"
	}
	
	/// Adds a blur filter to the view’s `CALayer`, applying the provided radius. Removes the filter when set to `nil`.
	/// Can be animated.
	public var blurRadius: CGFloat? {
		get {
			blurFilter?.value(forKey: blurRadiusKey) as? CGFloat
		}
		set {
			let startValue = blurRadius ?? 0
			let endValue = newValue ?? 0
			guard newValue != blurRadius else { return }
			guard let newValue else {
				// TODO: Maybe remove filter after any animations have completed
				removeBlurFilter()
				return
			}
			if blurFilter == nil {
				addBlurFilter()
			}

			addBlurActionIfNeeded(from: startValue, to: endValue)
			layer.setValue(newValue, forKeyPath: blurKeyPath)
		}
	}
}

extension UIView {
	public func addBlurActionIfNeeded(from startValue: CGFloat, to endValue: CGFloat) {
		guard
			let action = action(for: layer, forKey: "backgroundColor") as? NSObject,
			let animation = action.copy() as? CABasicAnimation
		else {
			return
		}
		animation.keyPath = blurKeyPath
		animation.fromValue = startValue
		animation.toValue = endValue
		layer.add(animation, forKey: "blurAnimation")
	}
}

extension UIView {
	private var blurFilter: NSObject? {
		layer.filters?.first as? NSObject
	}

	private func addBlurFilter() {
		guard let gaussianBlurFilter = Self.createBlurFilter() else {
			return
		}
		gaussianBlurFilter.setValue(blurFilterName, forKey: "name")
		gaussianBlurFilter.setValue(0, forKey: blurRadiusKey)
		layer.filters = [gaussianBlurFilter]
	}

	private func removeBlurFilter() {
		layer.filters = []
	}
}

extension UIView {
	private static func decodedString(forKey key: String) -> String? {
		Data(base64Encoded: key).flatMap { String(data: $0, encoding: .utf8) }
	}

	private static func createBlurFilter() -> NSObject? {
		// Shenanigans
		guard let selectorName = decodedString(forKey: "ZmlsdGVyV2l0aFR5cGU6") else {
			return nil
		}
		let filterWithTypeSelector = Selector(selectorName)
		guard
			let className = decodedString(forKey: "Q0FGaWx0ZXI="),
			let filterClass = NSClassFromString(className) as AnyObject as? NSObjectProtocol,
			filterClass.responds(to: filterWithTypeSelector)
		else {
			return nil
		}

		guard
			let filterType = decodedString(forKey: "Z2F1c3NpYW5CbHVy"),
			let gaussianBlurFilter = filterClass
				.perform(filterWithTypeSelector, with: filterType)
				.takeUnretainedValue() as? NSObject else {
			return nil
		}
		return gaussianBlurFilter
	}
}