//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation

public enum LinkValidator {
    public static func canParseURLs(in entireMessage: String) -> Bool {
        if entireMessage.unicodeScalars.contains(where: isProblematicCodepointAnywhereInString(_:)) {
            return false
        }
        return true
    }

    public static func isValidLink(linkText: String) -> Bool {
        if linkText.unicodeScalars.contains(where: isProblematicCodepointInLink(_:)) {
            return false
        }
        return true
    }

    private static func isProblematicCodepointAnywhereInString(_ scalar: UnicodeScalar) -> Bool {
        switch scalar {
        case "\u{202C}", // POP DIRECTIONAL FORMATTING
            "\u{202D}", // LEFT-TO-RIGHT OVERRIDE
            "\u{202E}": // RIGHT-TO-LEFT OVERRIDE
            return true
        default:
            return false
        }
    }

    private static func isProblematicCodepointInLink(_ scalar: UnicodeScalar) -> Bool {
        if isProblematicCodepointAnywhereInString(scalar) {
            return true
        }
        switch scalar {
        case "\u{2500}"..."\u{25FF}": // Box Drawing, Block Elements, Geometric Shapes
            return true
        default:
            return false
        }
    }

    public static func firstLinkPreviewURL(in entireMessage: MessageBody) -> URL? {
        // Don't include link previews for oversize text messages.
        guard entireMessage.text.utf8.dropFirst(Int(kOversizeTextMessageSizeThreshold)).isEmpty else {
            return nil
        }

        guard canParseURLs(in: entireMessage.text) else {
            return nil
        }

        guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
            owsFailDebug("Could not create NSDataDetector")
            return nil
        }

        var result: URL?
        detector.enumerateMatches(
            in: entireMessage.text,
            range: entireMessage.text.entireRange
        ) { match, _, stop in
            guard let match = match else { return }
            guard let parsedUrl = match.url else { return }
            guard let matchedRange = Range(match.range, in: entireMessage.text) else { return }
            for style in entireMessage.ranges.collapsedStyles {
                if
                    style.value.style.contains(style: .spoiler),
                    style.range.intersection(match.range)?.location ?? NSNotFound != NSNotFound
                {
                    return
                }
                // Styles are ordered; no need to search past the matched range.
                if style.range.lowerBound >= match.range.upperBound {
                    break
                }
            }
            guard parsedUrl.absoluteString.isEmpty.negated else { return }
            guard
                LinkPreviewHelper.isPermittedLinkPreviewUrl(
                    parsedUrl,
                    parsedFrom: String(entireMessage.text[matchedRange])
                )
            else {
                return
            }
            result = parsedUrl
            stop.pointee = true
        }
        return result
    }
}
