//
//  AsyncImageAttachment.swift
//  
//
//  Created by Stefan Compton on 4/29/22.
//

import Foundation
import UIKit

public class AsyncImageAttachment: NSTextAttachment {

    init(imageURL: URL, asyncImageHandler: AsyncImageHandler) {
        self.imageURL = imageURL
        self.asyncImageHandler = asyncImageHandler
        super.init(data: nil, ofType: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override public func attachmentBounds(
        for textContainer: NSTextContainer?,
        proposedLineFragment lineFrag: CGRect,
        glyphPosition position: CGPoint,
        characterIndex charIndex: Int
    ) -> CGRect {

        var imageSize = CGSize()

        let originalImageSize = image?.size ?? CGSize(width: 1, height: 1)
        let imageRatio = originalImageSize.height / originalImageSize.width

        let desiredWidth = originalImageSize.height / imageRatio
        if desiredWidth > lineFrag.width {
            imageSize.width = lineFrag.width
            imageSize.height = imageSize.width * imageRatio
        } else {
            imageSize.height = originalImageSize.height
            imageSize.width = desiredWidth
        }

        return CGRect(origin: CGPoint(), size: imageSize)
    }

    public override func image(
        forBounds imageBounds: CGRect,
        textContainer: NSTextContainer?,
        characterIndex charIndex: Int
    ) -> UIImage? {

        guard image == nil else {
            return image
        }

        asyncImageHandler.retrieveImage(with: imageURL) { [weak self] result in
            switch result {
            case .success(let asyncImage):
                guard let self = self else { return }
                self.image = asyncImage.image

                if let layoutManager = textContainer?.layoutManager,
                   let ranges = layoutManager.rangesForAttachment(self) {
                    ranges.forEach { range in
                        layoutManager.invalidateLayout(forCharacterRange: range, actualCharacterRange: nil)
                    }
                }
            case .failure(let error):
                print(error.localizedDescription)
            }
        }

        return nil
    }

    // MARK: - Private

    private let imageURL: URL
    private let asyncImageHandler: AsyncImageHandler
}

private extension NSLayoutManager {
    func rangesForAttachment(_ attachment: NSTextAttachment) -> [NSRange]? {
        guard let textStorage = textStorage else {
            return nil
        }
        var ranges: [NSRange] = []
        textStorage.enumerateAttribute(
            .attachment,
            in: NSRange(location: 0, length: textStorage.length),
            options: [],
            using: { attribute, range, _ in
                if let foundAttribute = attribute as? NSTextAttachment, foundAttribute === attachment {
                    ranges.append(range)
                }
            }
        )
        return ranges
    }
}
