// 
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import DTCoreText
import UIKit

@objcMembers
class HTMLFormatter: NSObject {
    /// Builds an attributed string from a string containing html.
    ///
    /// - Parameters:
    ///   - htmlString: The html string to use.
    ///   - allowedTags: The html tags that should be allowed.
    ///   - font: The default font to use.
    ///   - imageHandler: The image handler for the formatted string
    ///   - extraOptions: Extra (or override) options to apply for the format. See DTCoreText's documentation for available options.
    ///   - postFormatOperations: Optional block to provide operations to apply
    /// - Returns: The built `NSAttributedString`.
    /// - Note: It is recommended to include "p" and "body" tags in `allowedTags` as these are often added when parsing.
    static func formatHTML(_ htmlString: String,
                           withAllowedTags allowedTags: [String],
                           font: UIFont,
                           andImageHandler imageHandler: DTHTMLElement.ImageHandler? = nil,
                           extraOptions: [AnyHashable: Any] = [:],
                           postFormatOperations: ((NSMutableAttributedString) -> Void)? = nil) -> NSAttributedString {
        guard let data = htmlString.data(using: .utf8) else {
            return NSAttributedString(string: htmlString)
        }

        let sanitizeCallback: DTHTMLAttributedStringBuilderWillFlushCallback = { [allowedTags, font, imageHandler] (element: DTHTMLElement?) in
            element?.sanitize(with: allowedTags, bodyFont: font, imageHandler: imageHandler)
        }

        var options: [AnyHashable: Any] = [
            DTUseiOS6Attributes: true,
            DTDefaultFontDescriptor: font.fontDescriptor,
            DTDefaultLinkDecoration: false,
            DTDefaultLinkColor: ThemeService.shared().theme.colors.links,
            DTWillFlushBlockCallBack: sanitizeCallback
        ]
        options.merge(extraOptions) { (_, new) in new }

        guard let string = self.formatHTML(data, options: options) else {
            return NSAttributedString(string: htmlString)
        }

        let mutableString = NSMutableAttributedString(attributedString: string)
        MXKTools.removeDTCoreTextArtifacts(mutableString)
        postFormatOperations?(mutableString)
        
        // Remove CTForegroundColorFromContext attribute to fix the iOS 16 black link color issue
        // REF: https://github.com/Cocoanetics/DTCoreText/issues/792
        mutableString.removeAttribute(NSAttributedString.Key("CTForegroundColorFromContext"), range: NSRange(location: 0, length: mutableString.length))
        
        return mutableString
    }

    /// Builds an attributed string by replacing a `%@` placeholder with the supplied link text and URL.
    /// - Parameters:
    ///   - string: The string to be formatted.
    ///   - link: The link text to be inserted.
    ///   - url: The URL to be linked to.
    /// - Returns: An attributed string.
    static func format(_ string: String, with link: String, using url: URL) -> NSAttributedString {
        let baseString = NSMutableAttributedString(string: string)
        let attributedLink = NSAttributedString(string: link, attributes: [.link: url])
        
        let linkRange = (baseString.string as NSString).range(of: "%@")
        baseString.replaceCharacters(in: linkRange, with: attributedLink)
        
        return baseString
    }
}

extension HTMLFormatter {
    /// This replicates DTCoreText's NSAttributedString `initWithHTMLData`.
    /// It sets the sanitize callback on the builder from Swift to avoid EXC_BAD_ACCESS crashes.
    ///
    /// - Parameters:
    ///   - data: The data in HTML format from which to create the attributed string.
    ///   - options: Specifies how the document should be loaded.
    /// - Returns: Returns an initialized object, or `nil` if the data can’t be decoded.
    @objc static func formatHTML(_ data: Data,
                                 options: [AnyHashable: Any]) -> NSAttributedString? {
        guard !data.isEmpty else {
            return nil
        }

        let stringBuilder = DTHTMLAttributedStringBuilder(html: data,
                                                          options: options,
                                                          // DTCoreText doesn't use document attributes anyway
                                                          documentAttributes: nil)

        if let willFlushCallback = options[DTWillFlushBlockCallBack] as? DTHTMLAttributedStringBuilderWillFlushCallback {
            stringBuilder?.willFlushCallback = willFlushCallback
        }

        return stringBuilder?.generatedAttributedString()
    }
}
